Merge branch 'main' into zed2-breadcrumbs

Julia created

Change summary

Cargo.lock                                          |    2 
Cargo.toml                                          |    2 
assets/keymaps/default.json                         |    7 
crates/assistant/src/assistant_panel.rs             |   39 
crates/collab_ui2/src/collab_panel.rs               |    2 
crates/collab_ui2/src/collab_titlebar_item.rs       |    8 
crates/command_palette2/src/command_palette.rs      |    2 
crates/editor2/src/display_map/wrap_map.rs          |    2 
crates/editor2/src/editor.rs                        |  497 ---
crates/editor2/src/element.rs                       | 1035 ++------
crates/editor2/src/items.rs                         |  705 ++---
crates/editor2/src/mouse_context_menu.rs            |   98 
crates/editor2/src/selections_collection.rs         |    3 
crates/file_finder2/src/file_finder.rs              |    4 
crates/gpui2/src/app/test_context.rs                |   50 
crates/gpui2/src/elements/text.rs                   |   10 
crates/gpui2/src/elements/uniform_list.rs           |    5 
crates/gpui2/src/geometry.rs                        |   14 
crates/gpui2/src/platform.rs                        |    5 
crates/gpui2/src/platform/test/platform.rs          |    8 
crates/gpui2/src/platform/test/window.rs            |   22 
crates/gpui2/src/taffy.rs                           |   49 
crates/gpui2/src/view.rs                            |    8 
crates/gpui2/src/window.rs                          |   33 
crates/project_panel/src/project_panel.rs           |   14 
crates/project_panel2/src/project_panel.rs          |   29 
crates/search2/src/search.rs                        |   10 
crates/storybook2/src/stories.rs                    |    2 
crates/storybook2/src/stories/auto_height_editor.rs |   34 
crates/storybook2/src/story_selector.rs             |    4 
crates/theme_selector2/src/theme_selector.rs        |    8 
crates/ui2/src/components/button.rs                 |    0 
crates/ui2/src/components/button/button.rs          |    8 
crates/ui2/src/components/button/button_like.rs     |  108 
crates/ui2/src/components/button/icon_button.rs     |    6 
crates/ui2/src/components/list.rs                   |   66 
crates/ui2/src/components/list/list.rs              |   60 
crates/ui2/src/components/list/list_header.rs       |   46 
crates/ui2/src/components/list/list_item.rs         |   12 
crates/ui2/src/components/stories.rs                |    2 
crates/ui2/src/components/stories/button.rs         |   10 
crates/ui2/src/components/stories/list.rs           |    4 
crates/ui2/src/components/stories/list_header.rs    |   33 
crates/ui2/src/slot.rs                              |   12 
crates/ui2/src/ui2.rs                               |    2 
crates/welcome2/src/base_keymap_picker.rs           |    6 
crates/workspace2/Cargo.toml                        |   42 
crates/workspace2/src/item.rs                       |  664 ++--
crates/workspace2/src/pane.rs                       |   50 
crates/workspace2/src/pane_group.rs                 |    4 
crates/workspace2/src/persistence.rs                |    4 
crates/workspace2/src/persistence/model.rs          |    4 
crates/workspace2/src/searchable.rs                 |    2 
crates/workspace2/src/status_bar.rs                 |   25 
crates/workspace2/src/workspace2.rs                 | 1762 +++++++-------
crates/workspace2/src/workspace_settings.rs         |    2 
crates/zed2/src/main.rs                             |    4 
crates/zed2/src/zed2.rs                             |    4 
58 files changed, 2,518 insertions(+), 3,135 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -9987,7 +9987,7 @@ dependencies = [
 [[package]]
 name = "tree-sitter"
 version = "0.20.10"
-source = "git+https://github.com/tree-sitter/tree-sitter?rev=3b0159d25559b603af566ade3c83d930bf466db1#3b0159d25559b603af566ade3c83d930bf466db1"
+source = "git+https://github.com/tree-sitter/tree-sitter?rev=b5f461a69bf3df7298b1903574d506179e6390b0#b5f461a69bf3df7298b1903574d506179e6390b0"
 dependencies = [
  "cc",
  "regex",

Cargo.toml πŸ”—

@@ -202,7 +202,7 @@ tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", re
 tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
 
 [patch.crates-io]
-tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "3b0159d25559b603af566ade3c83d930bf466db1" }
+tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "b5f461a69bf3df7298b1903574d506179e6390b0" }
 async-task = { git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e" }
 
 # TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457

assets/keymaps/default.json πŸ”—

@@ -530,12 +530,17 @@
       "alt-cmd-shift-c": "project_panel::CopyRelativePath",
       "f2": "project_panel::Rename",
       "enter": "project_panel::Rename",
-      "space": "project_panel::Open",
       "backspace": "project_panel::Delete",
       "alt-cmd-r": "project_panel::RevealInFinder",
       "alt-shift-f": "project_panel::NewSearchInDirectory"
     }
   },
+  {
+    "context": "ProjectPanel && not_editing",
+    "bindings": {
+      "space": "project_panel::Open"
+    }
+  },
   {
     "context": "CollabPanel && not_editing",
     "bindings": {

crates/assistant/src/assistant_panel.rs πŸ”—

@@ -1218,6 +1218,31 @@ impl View for AssistantPanel {
         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",
@@ -1231,6 +1256,20 @@ impl View for AssistantPanel {
                         .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()

crates/collab_ui2/src/collab_panel.rs πŸ”—

@@ -2511,7 +2511,7 @@ impl CollabPanel {
                 } else {
                     el.child(
                         ListHeader::new(text)
-                            .when_some(button, |el, button| el.right_button(button))
+                            .when_some(button, |el, button| el.meta(button))
                             .selected(is_selected),
                     )
                 }

crates/collab_ui2/src/collab_titlebar_item.rs πŸ”—

@@ -37,7 +37,7 @@ use gpui::{
 };
 use project::Project;
 use theme::ActiveTheme;
-use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle2, IconButton, KeyBinding, Tooltip};
+use ui::{h_stack, prelude::*, Avatar, Button, ButtonStyle, IconButton, KeyBinding, Tooltip};
 use util::ResultExt;
 use workspace::{notifications::NotifyResultExt, Workspace};
 
@@ -154,7 +154,7 @@ impl Render for CollabTitlebarItem {
                             .id("project_owner_indicator")
                             .child(
                                 Button::new("player", "player")
-                                    .style(ButtonStyle2::Subtle)
+                                    .style(ButtonStyle::Subtle)
                                     .color(Some(Color::Player(0))),
                             )
                             .tooltip(move |cx| Tooltip::text("Toggle following", cx)),
@@ -167,7 +167,7 @@ impl Render for CollabTitlebarItem {
                             .id("titlebar_project_menu_button")
                             .child(
                                 Button::new("project_name", "project_name")
-                                    .style(ButtonStyle2::Subtle),
+                                    .style(ButtonStyle::Subtle),
                             )
                             .tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
                     )
@@ -179,7 +179,7 @@ impl Render for CollabTitlebarItem {
                             .id("titlebar_git_menu_button")
                             .child(
                                 Button::new("branch_name", "branch_name")
-                                    .style(ButtonStyle2::Subtle)
+                                    .style(ButtonStyle::Subtle)
                                     .color(Some(Color::Muted)),
                             )
                             .tooltip(move |cx| {

crates/command_palette2/src/command_palette.rs πŸ”—

@@ -11,7 +11,7 @@ use gpui::{
 };
 use picker::{Picker, PickerDelegate};
 
-use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, ListItem};
+use ui::{h_stack, prelude::*, v_stack, HighlightedLabel, KeyBinding, ListItem};
 use util::{
     channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
     ResultExt,

crates/editor2/src/display_map/wrap_map.rs πŸ”—

@@ -162,7 +162,7 @@ impl WrapMap {
                 {
                     let tab_snapshot = new_snapshot.tab_snapshot.clone();
                     let range = TabPoint::zero()..tab_snapshot.max_point();
-                    let edits = new_snapshot
+                    edits = new_snapshot
                         .update(
                             tab_snapshot,
                             &[TabEdit {

crates/editor2/src/editor.rs πŸ”—

@@ -63,6 +63,7 @@ use language::{
 use lazy_static::lazy_static;
 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::{
@@ -406,133 +407,17 @@ pub fn init_settings(cx: &mut AppContext) {
 
 pub fn init(cx: &mut AppContext) {
     init_settings(cx);
-    // cx.register_action_type(Editor::new_file);
-    // cx.register_action_type(Editor::new_file_in_direction);
-    // cx.register_action_type(Editor::cancel);
-    // cx.register_action_type(Editor::newline);
-    // cx.register_action_type(Editor::newline_above);
-    // cx.register_action_type(Editor::newline_below);
-    // cx.register_action_type(Editor::backspace);
-    // cx.register_action_type(Editor::delete);
-    // cx.register_action_type(Editor::tab);
-    // cx.register_action_type(Editor::tab_prev);
-    // cx.register_action_type(Editor::indent);
-    // cx.register_action_type(Editor::outdent);
-    // cx.register_action_type(Editor::delete_line);
-    // cx.register_action_type(Editor::join_lines);
-    // cx.register_action_type(Editor::sort_lines_case_sensitive);
-    // cx.register_action_type(Editor::sort_lines_case_insensitive);
-    // cx.register_action_type(Editor::reverse_lines);
-    // cx.register_action_type(Editor::shuffle_lines);
-    // cx.register_action_type(Editor::convert_to_upper_case);
-    // cx.register_action_type(Editor::convert_to_lower_case);
-    // cx.register_action_type(Editor::convert_to_title_case);
-    // cx.register_action_type(Editor::convert_to_snake_case);
-    // cx.register_action_type(Editor::convert_to_kebab_case);
-    // cx.register_action_type(Editor::convert_to_upper_camel_case);
-    // cx.register_action_type(Editor::convert_to_lower_camel_case);
-    // cx.register_action_type(Editor::delete_to_previous_word_start);
-    // cx.register_action_type(Editor::delete_to_previous_subword_start);
-    // cx.register_action_type(Editor::delete_to_next_word_end);
-    // cx.register_action_type(Editor::delete_to_next_subword_end);
-    // cx.register_action_type(Editor::delete_to_beginning_of_line);
-    // cx.register_action_type(Editor::delete_to_end_of_line);
-    // cx.register_action_type(Editor::cut_to_end_of_line);
-    // cx.register_action_type(Editor::duplicate_line);
-    // cx.register_action_type(Editor::move_line_up);
-    // cx.register_action_type(Editor::move_line_down);
-    // cx.register_action_type(Editor::transpose);
-    // cx.register_action_type(Editor::cut);
-    // cx.register_action_type(Editor::copy);
-    // cx.register_action_type(Editor::paste);
-    // cx.register_action_type(Editor::undo);
-    // cx.register_action_type(Editor::redo);
-    // cx.register_action_type(Editor::move_page_up);
-    // cx.register_action_type::<MoveDown>();
-    // cx.register_action_type(Editor::move_page_down);
-    // cx.register_action_type(Editor::next_screen);
-    // cx.register_action_type::<MoveLeft>();
-    // cx.register_action_type::<MoveRight>();
-    // cx.register_action_type(Editor::move_to_previous_word_start);
-    // cx.register_action_type(Editor::move_to_previous_subword_start);
-    // cx.register_action_type(Editor::move_to_next_word_end);
-    // cx.register_action_type(Editor::move_to_next_subword_end);
-    // cx.register_action_type(Editor::move_to_beginning_of_line);
-    // cx.register_action_type(Editor::move_to_end_of_line);
-    // cx.register_action_type(Editor::move_to_start_of_paragraph);
-    // cx.register_action_type(Editor::move_to_end_of_paragraph);
-    // cx.register_action_type(Editor::move_to_beginning);
-    // cx.register_action_type(Editor::move_to_end);
-    // cx.register_action_type(Editor::select_up);
-    // cx.register_action_type(Editor::select_down);
-    // cx.register_action_type(Editor::select_left);
-    // cx.register_action_type(Editor::select_right);
-    // cx.register_action_type(Editor::select_to_previous_word_start);
-    // cx.register_action_type(Editor::select_to_previous_subword_start);
-    // cx.register_action_type(Editor::select_to_next_word_end);
-    // cx.register_action_type(Editor::select_to_next_subword_end);
-    // cx.register_action_type(Editor::select_to_beginning_of_line);
-    // cx.register_action_type(Editor::select_to_end_of_line);
-    // cx.register_action_type(Editor::select_to_start_of_paragraph);
-    // cx.register_action_type(Editor::select_to_end_of_paragraph);
-    // cx.register_action_type(Editor::select_to_beginning);
-    // cx.register_action_type(Editor::select_to_end);
-    // cx.register_action_type(Editor::select_all);
-    // cx.register_action_type(Editor::select_all_matches);
-    // cx.register_action_type(Editor::select_line);
-    // cx.register_action_type(Editor::split_selection_into_lines);
-    // cx.register_action_type(Editor::add_selection_above);
-    // cx.register_action_type(Editor::add_selection_below);
-    // cx.register_action_type(Editor::select_next);
-    // cx.register_action_type(Editor::select_previous);
-    // cx.register_action_type(Editor::toggle_comments);
-    // cx.register_action_type(Editor::select_larger_syntax_node);
-    // cx.register_action_type(Editor::select_smaller_syntax_node);
-    // cx.register_action_type(Editor::move_to_enclosing_bracket);
-    // cx.register_action_type(Editor::undo_selection);
-    // cx.register_action_type(Editor::redo_selection);
-    // cx.register_action_type(Editor::go_to_diagnostic);
-    // cx.register_action_type(Editor::go_to_prev_diagnostic);
-    // cx.register_action_type(Editor::go_to_hunk);
-    // cx.register_action_type(Editor::go_to_prev_hunk);
-    // cx.register_action_type(Editor::go_to_definition);
-    // cx.register_action_type(Editor::go_to_definition_split);
-    // cx.register_action_type(Editor::go_to_type_definition);
-    // cx.register_action_type(Editor::go_to_type_definition_split);
-    // cx.register_action_type(Editor::fold);
-    // cx.register_action_type(Editor::fold_at);
-    // cx.register_action_type(Editor::unfold_lines);
-    // cx.register_action_type(Editor::unfold_at);
-    // cx.register_action_type(Editor::gutter_hover);
-    // cx.register_action_type(Editor::fold_selected_ranges);
-    // cx.register_action_type(Editor::show_completions);
-    // cx.register_action_type(Editor::toggle_code_actions);
-    // cx.register_action_type(Editor::open_excerpts);
-    // cx.register_action_type(Editor::toggle_soft_wrap);
-    // cx.register_action_type(Editor::toggle_inlay_hints);
-    // cx.register_action_type(Editor::reveal_in_finder);
-    // cx.register_action_type(Editor::copy_path);
-    // cx.register_action_type(Editor::copy_relative_path);
-    // cx.register_action_type(Editor::copy_highlight_json);
-    // cx.add_async_action(Editor::format);
-    // cx.register_action_type(Editor::restart_language_server);
-    // cx.register_action_type(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.register_action_type(Editor::next_copilot_suggestion);
-    // cx.register_action_type(Editor::previous_copilot_suggestion);
-    // cx.register_action_type(Editor::copilot_suggest);
-    // cx.register_action_type(Editor::context_menu_first);
-    // cx.register_action_type(Editor::context_menu_prev);
-    // cx.register_action_type(Editor::context_menu_next);
-    // cx.register_action_type(Editor::context_menu_last);
 
     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();
 }
 
 trait InvalidationRegion {
@@ -620,8 +505,6 @@ 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<Model<Project>>,
     collaboration_hub: Option<Box<dyn CollaborationHub>>,
     blink_manager: Model<BlinkManager>,
@@ -635,7 +518,7 @@ pub struct Editor {
     inlay_background_highlights: TreeMap<Option<TypeId>, InlayBackgroundHighlight>,
     nav_history: Option<ItemNavHistory>,
     context_menu: RwLock<Option<ContextMenu>>,
-    // mouse_context_menu: View<context_menu::ContextMenu>,
+    mouse_context_menu: Option<MouseContextMenu>,
     completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
     next_completion_id: CompletionId,
     available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
@@ -1729,21 +1612,11 @@ impl Editor {
     //         Self::new(EditorMode::Full, buffer, None, field_editor_style, cx)
     //     }
 
-    //     pub fn auto_height(
-    //         max_lines: usize,
-    //         field_editor_style: Option<Arc<GetFieldEditorTheme>>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) -> Self {
-    //         let buffer = cx.build_model(|cx| Buffer::new(0, cx.model_id() as u64, String::new()));
-    //         let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
-    //         Self::new(
-    //             EditorMode::AutoHeight { max_lines },
-    //             buffer,
-    //             None,
-    //             field_editor_style,
-    //             cx,
-    //         )
-    //     }
+    pub fn auto_height(max_lines: usize, cx: &mut ViewContext<Self>) -> Self {
+        let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), String::new()));
+        let buffer = cx.build_model(|cx| MultiBuffer::singleton(buffer, cx));
+        Self::new(EditorMode::AutoHeight { max_lines }, buffer, None, cx)
+    }
 
     pub fn for_buffer(
         buffer: Model<Buffer>,
@@ -1763,14 +1636,7 @@ impl Editor {
     }
 
     pub fn clone(&self, cx: &mut ViewContext<Self>) -> Self {
-        let mut clone = Self::new(
-            self.mode,
-            self.buffer.clone(),
-            self.project.clone(),
-            // todo!
-            // self.get_field_editor_theme.clone(),
-            cx,
-        );
+        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| {
@@ -1787,17 +1653,11 @@ impl Editor {
         mode: EditorMode,
         buffer: Model<MultiBuffer>,
         project: Option<Model<Project>>,
-        // todo!()
-        // get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        // let editor_view_id = cx.view_id();
         let style = cx.text_style();
         let font_size = style.font_size.to_pixels(cx.rem_size());
         let display_map = cx.build_model(|cx| {
-            // todo!()
-            // let settings = settings::get::<ThemeSettings>(cx);
-            // let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
             DisplayMap::new(buffer.clone(), style.font(), font_size, None, 2, 1, cx)
         });
 
@@ -1853,7 +1713,6 @@ impl Editor {
             ime_transaction: Default::default(),
             active_diagnostics: None,
             soft_wrap_mode_override,
-            // get_field_editor_theme,
             collaboration_hub: project.clone().map(|project| Box::new(project) as _),
             project,
             blink_manager: blink_manager.clone(),
@@ -1867,8 +1726,7 @@ impl Editor {
             inlay_background_highlights: Default::default(),
             nav_history: None,
             context_menu: RwLock::new(None),
-            // mouse_context_menu: cx
-            //     .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)),
+            mouse_context_menu: None,
             completion_tasks: Default::default(),
             next_completion_id: 0,
             next_inlay_id: 0,
@@ -1877,7 +1735,6 @@ impl Editor {
             document_highlights_task: Default::default(),
             pending_rename: Default::default(),
             searchable: true,
-            // override_text_style: None,
             cursor_shape: Default::default(),
             autoindent_mode: Some(AutoindentMode::EachLine),
             collapse_matches: false,
@@ -1995,25 +1852,25 @@ impl Editor {
         }
     }
 
-    //     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.add_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.build_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()
@@ -8369,6 +8226,18 @@ impl Editor {
         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);
+    }
+
     pub 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))
@@ -8791,62 +8660,56 @@ impl Editor {
     //         self.searchable
     //     }
 
-    //     fn open_excerpts(workspace: &mut Workspace, _: &OpenExcerpts, cx: &mut ViewContext<Workspace>) {
-    //         let active_item = workspace.active_item(cx);
-    //         let editor_handle = if let Some(editor) = active_item
-    //             .as_ref()
-    //             .and_then(|item| item.act_as::<Self>(cx))
-    //         {
-    //             editor
-    //         } else {
-    //             cx.propagate();
-    //             return;
-    //         };
-
-    //         let editor = editor_handle.read(cx);
-    //         let buffer = editor.buffer.read(cx);
-    //         if buffer.is_singleton() {
-    //             cx.propagate();
-    //             return;
-    //         }
+    fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
+        let buffer = self.buffer.read(cx);
+        if buffer.is_singleton() {
+            cx.propagate();
+            return;
+        }
 
-    //         let mut new_selections_by_buffer = HashMap::default();
-    //         for selection in editor.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)
-    //             }
-    //         }
+        let Some(workspace) = self.workspace() else {
+            cx.propagate();
+            return;
+        };
 
-    //         editor_handle.update(cx, |editor, cx| {
-    //             editor.push_to_nav_history(editor.selections.newest_anchor().head(), None, cx);
-    //         });
-    //         let pane = workspace.active_pane().clone();
-    //         pane.update(cx, |pane, _| pane.disable_history());
-
-    //         // 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.defer(move |workspace, cx| {
-    //             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());
-    //         });
-    //     }
+        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,
@@ -9392,7 +9255,7 @@ impl Render for Editor {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let settings = ThemeSettings::get_global(cx);
         let text_style = match self.mode {
-            EditorMode::SingleLine => TextStyle {
+            EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
                 color: cx.theme().colors().text,
                 font_family: settings.ui_font.family.clone(),
                 font_features: settings.ui_font.features,
@@ -9405,8 +9268,6 @@ impl Render for Editor {
                 white_space: WhiteSpace::Normal,
             },
 
-            EditorMode::AutoHeight { max_lines } => todo!(),
-
             EditorMode::Full => TextStyle {
                 color: cx.theme().colors().text,
                 font_family: settings.buffer_font.family.clone(),
@@ -9441,106 +9302,6 @@ impl Render for Editor {
     }
 }
 
-// impl View for Editor {
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let style = self.style(cx);
-//         let font_changed = self.display_map.update(cx, |map, cx| {
-//             map.set_fold_ellipses_color(style.folds.ellipses.text_color);
-//             map.set_font_with_size(style.text.font_id, style.text.font_size, cx)
-//         });
-
-//         if font_changed {
-//             cx.defer(move |editor, cx: &mut ViewContext<Editor>| {
-//                 hide_hover(editor, cx);
-//                 hide_link_definition(editor, cx);
-//             });
-//         }
-
-//         Stack::new()
-//             .with_child(EditorElement::new(style.clone()))
-//             .with_child(ChildView::new(&self.mouse_context_menu, cx))
-//             .into_any()
-//     }
-
-//     fn ui_name() -> &'static str {
-//         "Editor"
-//     }
-
-//     fn focus_in(&mut self, focused: AnyView, cx: &mut ViewContext<Self>) {
-//         if cx.is_self_focused() {
-//             let focused_event = EditorFocused(cx.handle());
-//             cx.emit(Event::Focused);
-//             cx.emit_global(focused_event);
-//         }
-//         if let Some(rename) = self.pending_rename.as_ref() {
-//             cx.focus(&rename.editor);
-//         } else if cx.is_self_focused() || !focused.is::<Editor>() {
-//             if !self.focused {
-//                 self.blink_manager.update(cx, BlinkManager::enable);
-//             }
-//             self.focused = true;
-//             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,
-//                     );
-//                 }
-//             });
-//         }
-//     }
-
-//     fn focus_out(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-//         let blurred_event = EditorBlurred(cx.handle());
-//         cx.emit_global(blurred_event);
-//         self.focused = false;
-//         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(Event::Blurred);
-//         cx.notify();
-//     }
-
-//     fn modifiers_changed(
-//         &mut self,
-//         event: &gpui::platform::ModifiersChangedEvent,
-//         cx: &mut ViewContext<Self>,
-//     ) -> bool {
-//         let pending_selection = self.has_pending_selection();
-
-//         if let Some(point) = &self.link_go_to_definition_state.last_trigger_point {
-//             if event.cmd && !pending_selection {
-//                 let point = point.clone();
-//                 let snapshot = self.snapshot(cx);
-//                 let kind = point.definition_kind(event.shift);
-
-//                 show_link_definition(kind, self, point, snapshot, cx);
-//                 return false;
-//             }
-//         }
-
-//         {
-//             if self.link_go_to_definition_state.symbol_range.is_some()
-//                 || !self.link_go_to_definition_state.definitions.is_empty()
-//             {
-//                 self.link_go_to_definition_state.symbol_range.take();
-//                 self.link_go_to_definition_state.definitions.clear();
-//                 cx.notify();
-//             }
-
-//             self.link_go_to_definition_state.task = None;
-
-//             self.clear_highlights::<LinkGoToDefinitionState>(cx);
-//         }
-
-//         false
-//     }
-
 impl InputHandler for Editor {
     fn text_for_range(
         &mut self,
@@ -9787,72 +9548,6 @@ impl InputHandler for Editor {
     }
 }
 
-// fn build_style(
-//     settings: &ThemeSettings,
-//     get_field_editor_theme: Option<&GetFieldEditorTheme>,
-//     override_text_style: Option<&OverrideTextStyle>,
-//     cx: &mut AppContext,
-// ) -> EditorStyle {
-//     let font_cache = cx.font_cache();
-//     let line_height_scalar = settings.line_height();
-//     let theme_id = settings.theme.meta.id;
-//     let mut theme = settings.theme.editor.clone();
-//     let mut style = if let Some(get_field_editor_theme) = get_field_editor_theme {
-//         let field_editor_theme = get_field_editor_theme(&settings.theme);
-//         theme.text_color = field_editor_theme.text.color;
-//         theme.selection = field_editor_theme.selection;
-//         theme.background = field_editor_theme
-//             .container
-//             .background_color
-//             .unwrap_or_default();
-//         EditorStyle {
-//             text: field_editor_theme.text,
-//             placeholder_text: field_editor_theme.placeholder_text,
-//             line_height_scalar,
-//             theme,
-//             theme_id,
-//         }
-//     } else {
-//         todo!();
-//         // 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 = font_cache
-//         //     .select_font(font_family_id, &font_properties)
-//         //     .unwrap();
-//         // let font_size = settings.buffer_font_size(cx);
-//         // EditorStyle {
-//         //     text: TextStyle {
-//         //         color: settings.theme.editor.text_color,
-//         //         font_family_name,
-//         //         font_family_id,
-//         //         font_id,
-//         //         font_size,
-//         //         font_properties,
-//         //         underline: Default::default(),
-//         //         soft_wrap: false,
-//         //     },
-//         //     placeholder_text: None,
-//         //     line_height_scalar,
-//         //     theme,
-//         //     theme_id,
-//         // }
-//     };
-
-//     if let Some(highlight_style) = override_text_style.and_then(|build_style| build_style(&style)) {
-//         if let Some(highlighted) = style
-//             .text
-//             .clone()
-//             .highlight(highlight_style, font_cache)
-//             .log_err()
-//         {
-//             style.text = highlighted;
-//         }
-//     }
-
-//     style
-// }
-
 trait SelectionExt {
     fn offset_range(&self, buffer: &MultiBufferSnapshot) -> Range<usize>;
     fn point_range(&self, buffer: &MultiBufferSnapshot) -> Range<Point>;

crates/editor2/src/element.rs πŸ”—

@@ -9,9 +9,11 @@ use crate::{
         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,
     scroll::scroll_amount::ScrollAmount,
     CursorShape, DisplayPoint, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
     HalfPageDown, HalfPageUp, LineDown, LineUp, MoveDown, OpenExcerpts, PageDown, PageUp, Point,
@@ -19,14 +21,15 @@ use crate::{
 };
 use anyhow::Result;
 use collections::{BTreeMap, HashMap};
+use git::diff::DiffHunkStatus;
 use gpui::{
-    div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
-    BorrowWindow, Bounds, ContentMask, Corners, DispatchPhase, Edges, Element, ElementId,
-    ElementInputHandler, Entity, EntityId, Hsla, InteractiveBounds, InteractiveElement,
-    IntoElement, LineLayout, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
-    ParentElement, Pixels, RenderOnce, ScrollWheelEvent, ShapedLine, SharedString, Size,
-    StackingOrder, StatefulInteractiveElement, Style, Styled, TextRun, TextStyle, View,
-    ViewContext, WeakView, WindowContext, WrappedLine,
+    div, overlay, point, px, relative, size, transparent_black, Action, AnchorCorner, AnyElement,
+    AsyncWindowContext, AvailableSpace, BorrowWindow, Bounds, ContentMask, Corners, CursorStyle,
+    DispatchPhase, Edges, Element, ElementId, ElementInputHandler, Entity, EntityId, Hsla,
+    InteractiveBounds, InteractiveElement, IntoElement, LineLayout, ModifiersChangedEvent,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, RenderOnce,
+    ScrollWheelEvent, ShapedLine, SharedString, Size, StackingOrder, StatefulInteractiveElement,
+    Style, Styled, TextRun, TextStyle, View, ViewContext, WeakView, WindowContext, WrappedLine,
 };
 use itertools::Itertools;
 use language::language_settings::ShowWhitespaceSetting;
@@ -139,8 +142,6 @@ impl EditorElement {
         register_action(view, cx, Editor::move_right);
         register_action(view, cx, Editor::move_down);
         register_action(view, cx, Editor::move_up);
-        // on_action(cx, Editor::new_file); todo!()
-        // on_action(cx, Editor::new_file_in_direction); todo!()
         register_action(view, cx, Editor::cancel);
         register_action(view, cx, Editor::newline);
         register_action(view, cx, Editor::newline_above);
@@ -263,7 +264,7 @@ impl EditorElement {
         register_action(view, cx, Editor::fold_selected_ranges);
         register_action(view, cx, Editor::show_completions);
         register_action(view, cx, Editor::toggle_code_actions);
-        // on_action(cx, Editor::open_excerpts); todo!()
+        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);
@@ -312,7 +313,57 @@ impl EditorElement {
         register_action(view, cx, Editor::context_menu_last);
     }
 
-    fn mouse_down(
+    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();
+                }
+            }
+        });
+    }
+
+    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,
@@ -365,25 +416,25 @@ impl EditorElement {
         true
     }
 
-    // fn mouse_right_down(
-    //     editor: &mut Editor,
-    //     position: gpui::Point<Pixels>,
-    //     position_map: &PositionMap,
-    //     text_bounds: Bounds<Pixels>,
-    //     cx: &mut EventContext<Editor>,
-    // ) -> bool {
-    //     if !text_bounds.contains_point(position) {
-    //         return false;
-    //     }
-    //     let point_for_position = position_map.point_for_position(text_bounds, position);
-    //     mouse_context_menu::deploy_context_menu(
-    //         editor,
-    //         position,
-    //         point_for_position.previous_valid,
-    //         cx,
-    //     );
-    //     true
-    // }
+    fn mouse_right_down(
+        editor: &mut Editor,
+        event: &MouseDownEvent,
+        position_map: &PositionMap,
+        text_bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        if !text_bounds.contains_point(&event.position) {
+            return false;
+        }
+        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,
+        );
+        true
+    }
 
     fn mouse_up(
         editor: &mut Editor,
@@ -725,87 +776,85 @@ impl EditorElement {
     }
 
     fn paint_diff_hunks(bounds: Bounds<Pixels>, layout: &LayoutState, cx: &mut WindowContext) {
-        // todo!()
-        // let diff_style = &theme::current(cx).editor.diff.clone();
-        // 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 = diff_style.removed_width_em * line_height;
-        //             let highlight_origin = bounds.origin + point(-width, start_y);
-        //             let highlight_size = point(width * 2., end_y - start_y);
-        //             let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
-        //             cx.paint_quad(Quad {
-        //                 bounds: highlight_bounds,
-        //                 background: Some(diff_style.modified),
-        //                 border: Border::new(0., Color::transparent_black()).into(),
-        //                 corner_radii: (1. * line_height).into(),
-        //             });
-
-        //             continue;
-        //         }
-
-        //         DisplayDiffHunk::Unfolded {
-        //             display_row_range,
-        //             status,
-        //         } => (display_row_range, status),
-        //     };
-
-        //     let color = match status {
-        //         DiffHunkStatus::Added => diff_style.inserted,
-        //         DiffHunkStatus::Modified => diff_style.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 = diff_style.removed_width_em * line_height;
-        //             let highlight_origin = bounds.origin + point(-width, start_y);
-        //             let highlight_size = point(width * 2., end_y - start_y);
-        //             let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
-        //             cx.paint_quad(Quad {
-        //                 bounds: highlight_bounds,
-        //                 background: Some(diff_style.deleted),
-        //                 border: Border::new(0., Color::transparent_black()).into(),
-        //                 corner_radii: (1. * line_height).into(),
-        //             });
-
-        //             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 = diff_style.width_em * line_height;
-        //     let highlight_origin = bounds.origin + point(-width, start_y);
-        //     let highlight_size = point(width * 2., end_y - start_y);
-        //     let highlight_bounds = Bounds::<Pixels>::new(highlight_origin, highlight_size);
-
-        //     cx.paint_quad(Quad {
-        //         bounds: highlight_bounds,
-        //         background: Some(color),
-        //         border: Border::new(0., Color::transparent_black()).into(),
-        //         corner_radii: (diff_style.corner_radius * line_height).into(),
-        //     });
-        // }
+        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(
+                        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 => gpui::green(), // todo!("use the appropriate color")
+                DiffHunkStatus::Modified => gpui::yellow(), // todo!("use the appropriate color")
+
+                //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(
+                        highlight_bounds,
+                        Corners::all(1. * line_height),
+                        gpui::red(), // todo!("use the right color")
+                        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(
+                highlight_bounds,
+                Corners::all(0.05 * line_height),
+                color, // todo!("use the right color")
+                Edges::default(),
+                transparent_black(),
+            );
+        }
     }
 
     fn paint_text(
@@ -831,15 +880,19 @@ impl EditorElement {
                 bounds: text_bounds,
             }),
             |cx| {
-                // todo!("cursor region")
-                // cx.scene().push_cursor_region(CursorRegion {
-                //     bounds,
-                //     style: if !editor.link_go_to_definition_state.definitions.is_empty {
-                //         CursorStyle::PointingHand
-                //     } else {
-                //         CursorStyle::IBeam
-                //     },
-                // });
+                if text_bounds.contains_point(&cx.mouse_position()) {
+                    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| {
@@ -1138,6 +1191,22 @@ impl EditorElement {
                             }
                         }
                     }
+
+                    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.draw(
+                            gpui::Point::default(),
+                            size(AvailableSpace::MinContent, AvailableSpace::MinContent),
+                            cx,
+                            |_, _| {},
+                        );
+                    }
                 })
             },
         )
@@ -1662,11 +1731,6 @@ impl EditorElement {
         cx: &mut WindowContext,
     ) -> LayoutState {
         self.editor.update(cx, |editor, cx| {
-            // let mut size = constraint.max;
-            // if size.x.is_infinite() {
-            //     unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-            // }
-
             let snapshot = editor.snapshot(cx);
             let style = self.style.clone();
 
@@ -1702,6 +1766,7 @@ impl EditorElement {
             };
 
             editor.gutter_width = gutter_width;
+
             let text_width = bounds.size.width - gutter_width;
             let overscroll = size(em_width, px(0.));
             let snapshot = {
@@ -1728,25 +1793,6 @@ impl EditorElement {
                 .collect::<SmallVec<[_; 2]>>();
 
             let scroll_height = Pixels::from(snapshot.max_point().row() + 1) * line_height;
-            // todo!("this should happen during layout")
-            let editor_mode = snapshot.mode;
-            if let EditorMode::AutoHeight { max_lines } = editor_mode {
-                todo!()
-                //     size.set_y(
-                //         scroll_height
-                //             .min(constraint.max_along(Axis::Vertical))
-                //             .max(constraint.min_along(Axis::Vertical))
-                //             .max(line_height)
-                //             .min(line_height * max_lines as f32),
-                //     )
-            } else if let EditorMode::SingleLine = editor_mode {
-                bounds.size.height = line_height.min(bounds.size.height);
-            }
-            // todo!()
-            // else if size.y.is_infinite() {
-            //     //     size.set_y(scroll_height);
-            // }
-            //
             let gutter_size = size(gutter_width, bounds.size.height);
             let text_size = size(text_width, bounds.size.height);
 
@@ -2064,7 +2110,7 @@ impl EditorElement {
                 .unwrap();
 
             LayoutState {
-                mode: editor_mode,
+                mode: snapshot.mode,
                 position_map: Arc::new(PositionMap {
                     size: bounds.size,
                     scroll_position: point(
@@ -2308,10 +2354,10 @@ impl EditorElement {
                     return;
                 }
 
-                let should_cancel = editor.update(cx, |editor, cx| {
+                let handled = editor.update(cx, |editor, cx| {
                     Self::scroll(editor, event, &position_map, &interactive_bounds, cx)
                 });
-                if should_cancel {
+                if handled {
                     cx.stop_propagation();
                 }
             }
@@ -2327,19 +2373,25 @@ impl EditorElement {
                     return;
                 }
 
-                let should_cancel = editor.update(cx, |editor, cx| {
-                    Self::mouse_down(
-                        editor,
-                        event,
-                        &position_map,
-                        text_bounds,
-                        gutter_bounds,
-                        &stacking_order,
-                        cx,
-                    )
-                });
+                let handled = 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)
+                    }),
+                    _ => false,
+                };
 
-                if should_cancel {
+                if handled {
                     cx.stop_propagation()
                 }
             }
@@ -2351,7 +2403,7 @@ impl EditorElement {
             let stacking_order = cx.stacking_order().clone();
 
             move |event: &MouseUpEvent, phase, cx| {
-                let should_cancel = editor.update(cx, |editor, cx| {
+                let handled = editor.update(cx, |editor, cx| {
                     Self::mouse_up(
                         editor,
                         event,
@@ -2362,26 +2414,11 @@ impl EditorElement {
                     )
                 });
 
-                if should_cancel {
+                if handled {
                     cx.stop_propagation()
                 }
             }
         });
-        //todo!()
-        // on_down(MouseButton::Right, {
-        //     let position_map = layout.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();
-        //         }
-        //     }
-        // });
         cx.on_mouse_event({
             let position_map = layout.position_map.clone();
             let editor = self.editor.clone();
@@ -2617,19 +2654,44 @@ impl Element for EditorElement {
         cx: &mut gpui::WindowContext,
     ) -> (gpui::LayoutId, Self::State) {
         self.editor.update(cx, |editor, cx| {
-            editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
+            editor.set_style(self.style.clone(), cx);
 
-            let rem_size = cx.rem_size();
-            let mut style = Style::default();
-            style.size.width = relative(1.).into();
-            style.size.height = match editor.mode {
+            let layout_id = match editor.mode {
                 EditorMode::SingleLine => {
-                    self.style.text.line_height_in_pixels(cx.rem_size()).into()
+                    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, available_space, 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)
                 }
-                EditorMode::AutoHeight { .. } => todo!(),
-                EditorMode::Full => relative(1.).into(),
             };
-            let layout_id = cx.request_layout(&style, None);
 
             (layout_id, ())
         })
@@ -2657,6 +2719,7 @@ impl Element for EditorElement {
         let dispatch_context = self.editor.read(cx).dispatch_context(cx);
         cx.with_key_dispatch(dispatch_context, Some(focus_handle.clone()), |_, cx| {
             self.register_actions(cx);
+            self.register_key_listeners(cx);
 
             // We call with_z_index to establish a new stacking context.
             cx.with_z_index(0, |cx| {
@@ -2698,604 +2761,6 @@ impl IntoElement for EditorElement {
     }
 }
 
-// impl EditorElement {
-//     type LayoutState = LayoutState;
-//     type PaintState = ();
-
-//     fn layout(
-//         &mut self,
-//         constraint: SizeConstraint,
-//         editor: &mut Editor,
-//         cx: &mut ViewContext<Editor>,
-//     ) -> (gpui::Point<Pixels>, Self::LayoutState) {
-//         let mut size = constraint.max;
-//         if size.x.is_infinite() {
-//             unimplemented!("we don't yet handle an infinite width constraint on buffer elements");
-//         }
-
-//         let snapshot = editor.snapshot(cx);
-//         let style = self.style.clone();
-
-//         let line_height = (style.text.font_size * style.line_height_scalar).round();
-
-//         let gutter_padding;
-//         let gutter_width;
-//         let gutter_margin;
-//         if snapshot.show_gutter {
-//             let em_width = style.text.em_width(cx.font_cache());
-//             gutter_padding = (em_width * style.gutter_padding_factor).round();
-//             gutter_width = self.max_line_number_width(&snapshot, cx) + gutter_padding * 2.0;
-//             gutter_margin = -style.text.descent(cx.font_cache());
-//         } else {
-//             gutter_padding = 0.0;
-//             gutter_width = 0.0;
-//             gutter_margin = 0.0;
-//         };
-
-//         let text_width = size.x - gutter_width;
-//         let em_width = style.text.em_width(cx.font_cache());
-//         let em_advance = style.text.em_advance(cx.font_cache());
-//         let overscroll = point(em_width, 0.);
-//         let snapshot = {
-//             editor.set_visible_line_count(size.y / line_height, cx);
-
-//             let editor_width = text_width - gutter_margin - overscroll.x - 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();
-
-//         let scroll_height = (snapshot.max_point().row() + 1) as f32 * line_height;
-//         if let EditorMode::AutoHeight { max_lines } = snapshot.mode {
-//             size.set_y(
-//                 scroll_height
-//                     .min(constraint.max_along(Axis::Vertical))
-//                     .max(constraint.min_along(Axis::Vertical))
-//                     .max(line_height)
-//                     .min(line_height * max_lines as f32),
-//             )
-//         } else if let EditorMode::SingleLine = snapshot.mode {
-//             size.set_y(line_height.max(constraint.min_along(Axis::Vertical)))
-//         } else if size.y.is_infinite() {
-//             size.set_y(scroll_height);
-//         }
-//         let gutter_size = point(gutter_width, size.y);
-//         let text_size = point(text_width, size.y);
-
-//         let autoscroll_horizontally = editor.autoscroll_vertically(size.y, 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 = size.y / 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<(SelectionStyle, Vec<SelectionLayout>)> = Vec::new();
-//         let mut active_rows = BTreeMap::new();
-//         let mut fold_ranges = Vec::new();
-//         let is_singleton = editor.is_singleton(cx);
-
-//         let highlighted_rows = editor.highlighted_rows();
-//         let theme = theme::current(cx);
-//         let highlighted_ranges = editor.background_highlights_in_range(
-//             start_anchor..end_anchor,
-//             &snapshot.display_snapshot,
-//             theme.as_ref(),
-//         );
-
-//         fold_ranges.extend(
-//             snapshot
-//                 .folds_in_range(start_anchor..end_anchor)
-//                 .map(|anchor| {
-//                     let start = anchor.start.to_point(&snapshot.buffer_snapshot);
-//                     (
-//                         start.row,
-//                         start.to_display_point(&snapshot.display_snapshot)
-//                             ..anchor.end.to_display_point(&snapshot),
-//                     )
-//                 }),
-//         );
-
-//         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.selection, 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 =
-//                                 style.selection_style_for_room_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 {
-//                     style.selection_style_for_room_participant(participant_index.0)
-//                 } else {
-//                     style.absent_selection
-//                 };
-
-//                 // 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 = &settings::get::<EditorSettings>(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 fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Color)> = fold_ranges
-//             .into_iter()
-//             .map(|(id, fold)| {
-//                 let color = self
-//                     .style
-//                     .folds
-//                     .ellipses
-//                     .background
-//                     .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
-//                     .color;
-
-//                 (id, fold, color)
-//             })
-//             .collect();
-
-//         let head_for_relative = newest_selection_head.unwrap_or_else(|| {
-//             let newest = editor.selections.newest::<Point>(cx);
-//             SelectionLayout::new(
-//                 newest,
-//                 editor.selections.line_mode,
-//                 editor.cursor_shape,
-//                 &snapshot.display_snapshot,
-//                 true,
-//                 true,
-//             )
-//             .head
-//         });
-
-//         let (line_number_layouts, fold_statuses) = self.layout_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 = 0.0;
-//         let line_layouts =
-//             self.layout_lines(start_row..end_row, &line_number_layouts, &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 style = self.style.clone();
-//         let longest_line_width = layout_line(
-//             snapshot.longest_row(),
-//             &snapshot,
-//             &style,
-//             cx.text_layout_cache(),
-//         )
-//         .width();
-//         let scroll_width = longest_line_width.max(max_visible_line_width) + overscroll.x;
-//         let em_width = style.text.em_width(cx.font_cache());
-//         let (scroll_width, blocks) = self.layout_blocks(
-//             start_row..end_row,
-//             &snapshot,
-//             size.x,
-//             scroll_width,
-//             gutter_padding,
-//             gutter_width,
-//             em_width,
-//             gutter_width + gutter_margin,
-//             line_height,
-//             &style,
-//             &line_layouts,
-//             editor,
-//             cx,
-//         );
-
-//         let scroll_max = point(
-//             ((scroll_width - text_size.x) / 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.x,
-//                 scroll_width,
-//                 em_width,
-//                 &line_layouts,
-//                 cx,
-//             )
-//         } else {
-//             false
-//         };
-
-//         if clamped || autoscrolled {
-//             snapshot = editor.snapshot(cx);
-//         }
-
-//         let style = editor.style(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() {
-//                     context_menu =
-//                         editor.render_context_menu(newest_selection_head, style.clone(), 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(|indicator| (newest_selection_head.row(), indicator));
-//             }
-//         }
-
-//         let visible_rows = start_row..start_row + line_layouts.len() as u32;
-//         let mut hover = editor.hover_state.render(
-//             &snapshot,
-//             &style,
-//             visible_rows,
-//             editor.workspace.as_ref().map(|(w, _)| w.clone()),
-//             cx,
-//         );
-//         let mode = editor.mode;
-
-//         let mut fold_indicators = editor.render_fold_indicators(
-//             fold_statuses,
-//             &style,
-//             editor.gutter_hovered,
-//             line_height,
-//             gutter_margin,
-//             cx,
-//         );
-
-//         if let Some((_, context_menu)) = context_menu.as_mut() {
-//             context_menu.layout(
-//                 SizeConstraint {
-//                     min: gpui::Point::<Pixels>::zero(),
-//                     max: point(
-//                         cx.window_size().x * 0.7,
-//                         (12. * line_height).min((size.y - line_height) / 2.),
-//                     ),
-//                 },
-//                 editor,
-//                 cx,
-//             );
-//         }
-
-//         if let Some((_, indicator)) = code_actions_indicator.as_mut() {
-//             indicator.layout(
-//                 SizeConstraint::strict_along(
-//                     Axis::Vertical,
-//                     line_height * style.code_actions.vertical_scale,
-//                 ),
-//                 editor,
-//                 cx,
-//             );
-//         }
-
-//         for fold_indicator in fold_indicators.iter_mut() {
-//             if let Some(indicator) = fold_indicator.as_mut() {
-//                 indicator.layout(
-//                     SizeConstraint::strict_along(
-//                         Axis::Vertical,
-//                         line_height * style.code_actions.vertical_scale,
-//                     ),
-//                     editor,
-//                     cx,
-//                 );
-//             }
-//         }
-
-//         if let Some((_, hover_popovers)) = hover.as_mut() {
-//             for hover_popover in hover_popovers.iter_mut() {
-//                 hover_popover.layout(
-//                     SizeConstraint {
-//                         min: gpui::Point::<Pixels>::zero(),
-//                         max: point(
-//                             (120. * em_width) // Default size
-//                                 .min(size.x / 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(size.y / 2.) // Shrink to half of the editor height
-//                                 .max(MIN_POPOVER_LINE_HEIGHT * line_height), // Apply minimum height of 4 lines
-//                         ),
-//                     },
-//                     editor,
-//                     cx,
-//                 );
-//             }
-//         }
-
-//         let invisible_symbol_font_size = self.style.text.font_size / 2.0;
-//         let invisible_symbol_style = RunStyle {
-//             color: self.style.whitespace,
-//             font_id: self.style.text.font_id,
-//             underline: Default::default(),
-//         };
-
-//         (
-//             size,
-//             LayoutState {
-//                 mode,
-//                 position_map: Arc::new(PositionMap {
-//                     size,
-//                     scroll_max,
-//                     line_layouts,
-//                     line_height,
-//                     em_width,
-//                     em_advance,
-//                     snapshot,
-//                 }),
-//                 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,
-//                 fold_ranges,
-//                 line_number_layouts,
-//                 display_hunks,
-//                 blocks,
-//                 selections,
-//                 context_menu,
-//                 code_actions_indicator,
-//                 fold_indicators,
-//                 tab_invisible: cx.text_layout_cache().layout_str(
-//                     "β†’",
-//                     invisible_symbol_font_size,
-//                     &[("β†’".len(), invisible_symbol_style)],
-//                 ),
-//                 space_invisible: cx.text_layout_cache().layout_str(
-//                     "β€’",
-//                     invisible_symbol_font_size,
-//                     &[("β€’".len(), invisible_symbol_style)],
-//                 ),
-//                 hover_popovers: hover,
-//             },
-//         )
-//     }
-
-//     fn paint(
-//         &mut self,
-//         bounds: Bounds<Pixels>,
-//         visible_bounds: Bounds<Pixels>,
-//         layout: &mut Self::LayoutState,
-//         editor: &mut Editor,
-//         cx: &mut ViewContext<Editor>,
-//     ) -> Self::PaintState {
-//         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-//         cx.scene().push_layer(Some(visible_bounds));
-
-//         let gutter_bounds = Bounds::<Pixels>::new(bounds.origin, layout.gutter_size);
-//         let text_bounds = Bounds::<Pixels>::new(
-//             bounds.origin + point(layout.gutter_size.x, 0.0),
-//             layout.text_size,
-//         );
-
-//         Self::attach_mouse_handlers(
-//             &layout.position_map,
-//             layout.hover_popovers.is_some(),
-//             visible_bounds,
-//             text_bounds,
-//             gutter_bounds,
-//             bounds,
-//             cx,
-//         );
-
-//         self.paint_background(gutter_bounds, text_bounds, layout, cx);
-//         if layout.gutter_size.x > 0. {
-//             self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
-//         }
-//         self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
-
-//         cx.scene().push_layer(Some(bounds));
-//         if !layout.blocks.is_empty {
-//             self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
-//         }
-//         self.paint_scrollbar(bounds, layout, &editor, cx);
-//         cx.scene().pop_layer();
-//         cx.scene().pop_layer();
-//     }
-
-//     fn rect_for_text_range(
-//         &self,
-//         range_utf16: Range<usize>,
-//         bounds: Bounds<Pixels>,
-//         _: Bounds<Pixels>,
-//         layout: &Self::LayoutState,
-//         _: &Self::PaintState,
-//         _: &Editor,
-//         _: &ViewContext<Editor>,
-//     ) -> Option<Bounds<Pixels>> {
-//         let text_bounds = Bounds::<Pixels>::new(
-//             bounds.origin + point(layout.gutter_size.x, 0.0),
-//             layout.text_size,
-//         );
-//         let content_origin = text_bounds.origin + point(layout.gutter_margin, 0.);
-//         let scroll_position = layout.position_map.snapshot.scroll_position();
-//         let start_row = scroll_position.y as u32;
-//         let scroll_top = scroll_position.y * layout.position_map.line_height;
-//         let scroll_left = scroll_position.x * layout.position_map.em_width;
-
-//         let range_start = OffsetUtf16(range_utf16.start)
-//             .to_display_point(&layout.position_map.snapshot.display_snapshot);
-//         if range_start.row() < start_row {
-//             return None;
-//         }
-
-//         let line = &layout
-//             .position_map
-//             .line_layouts
-//             .get((range_start.row() - start_row) as usize)?
-//             .line;
-//         let range_start_x = line.x_for_index(range_start.column() as usize);
-//         let range_start_y = range_start.row() as f32 * layout.position_map.line_height;
-//         Some(Bounds::<Pixels>::new(
-//             content_origin
-//                 + point(
-//                     range_start_x,
-//                     range_start_y + layout.position_map.line_height,
-//                 )
-//                 - point(scroll_left, scroll_top),
-//             point(
-//                 layout.position_map.em_width,
-//                 layout.position_map.line_height,
-//             ),
-//         ))
-//     }
-
-//     fn debug(
-//         &self,
-//         bounds: Bounds<Pixels>,
-//         _: &Self::LayoutState,
-//         _: &Self::PaintState,
-//         _: &Editor,
-//         _: &ViewContext<Editor>,
-//     ) -> json::Value {
-//         json!({
-//             "type": "BufferElement",
-//             "bounds": bounds.to_json()
-//         })
-//     }
-// }
-
 type BufferRow = u32;
 
 pub struct LayoutState {

crates/editor2/src/items.rs πŸ”—

@@ -4,13 +4,14 @@ use crate::{
     EditorEvent, EditorSettings, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot,
     NavigationData, ToPoint as _,
 };
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
-    div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
-    FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
-    ViewContext, VisualContext, WeakView, WindowContext,
+    div, point, AnyElement, AppContext, AsyncAppContext, AsyncWindowContext, Context, Div, Entity,
+    EntityId, EventEmitter, FocusHandle, 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,
@@ -20,6 +21,7 @@ use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPat
 use rpc::proto::{self, update_view, PeerId};
 use settings::Settings;
 use smallvec::SmallVec;
+use std::fmt::Write;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -31,8 +33,11 @@ use std::{
 use text::Selection;
 use theme::{ActiveTheme, Theme};
 use ui::{Color, Label};
-use util::{paths::PathExt, ResultExt, TryFutureExt};
-use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
+use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use workspace::{
+    item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
+    StatusItemView,
+};
 use workspace::{
     item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
     searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -71,110 +76,108 @@ impl FollowableItem for Editor {
         workspace: View<Workspace>,
         remote_id: ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<Task<Result<View<Self>>>> {
-        todo!()
+        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.build_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.build_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)
+        }))
     }
-    //     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.read_with(&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.add_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.add_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;
@@ -195,7 +198,7 @@ impl FollowableItem for Editor {
         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
@@ -242,7 +245,7 @@ impl FollowableItem for Editor {
         &self,
         event: &Self::FollowableEvent,
         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()));
@@ -315,7 +318,7 @@ impl FollowableItem for Editor {
         })
     }
 
-    fn is_project_item(&self, _cx: &AppContext) -> bool {
+    fn is_project_item(&self, _cx: &WindowContext) -> bool {
         true
     }
 }
@@ -324,132 +327,129 @@ async fn update_editor_from_message(
     this: WeakView<Editor>,
     project: Model<Project>,
     message: proto::update_view::Editor,
-    cx: &mut AsyncAppContext,
+    cx: &mut AsyncWindowContext,
 ) -> Result<()> {
-    todo!()
+    // 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(())
 }
-// Previous implementation of the above
-//     // 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,
@@ -529,39 +529,38 @@ fn deserialize_anchor(buffer: &MultiBufferSnapshot, anchor: proto::EditorAnchor)
 
 impl Item for Editor {
     fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
-        todo!();
-        // 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
-        // }
+        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> {
@@ -768,7 +767,7 @@ impl Item for Editor {
         let cursor = self.selections.newest_anchor().head();
         let multibuffer = &self.buffer().read(cx);
         let (buffer_id, symbols) =
-            multibuffer.symbols_containing(cursor, Some(&cx.theme().styles.syntax), cx)?;
+            multibuffer.symbols_containing(cursor, Some(&variant.syntax()), cx)?;
         let buffer = multibuffer.buffer(buffer_id)?;
 
         let buffer = buffer.read(cx);
@@ -788,19 +787,9 @@ impl Item for Editor {
             text: filename,
             highlights: None,
         }];
-        breadcrumbs.extend(symbols.into_iter().map(|symbol| {
-            // eprintln!(
-            //     "ranges: {:?}",
-            //     symbol
-            //         .highlight_ranges
-            //         .iter()
-            //         .map(|range| &symbol.text[range.0.clone()])
-            //         .collect::<Vec<_>>()
-            // );
-            BreadcrumbText {
-                text: symbol.text,
-                highlights: Some(symbol.highlight_ranges),
-            }
+        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
+            text: symbol.text,
+            highlights: Some(symbol.highlight_ranges),
         }));
         Some(breadcrumbs)
     }
@@ -1129,86 +1118,78 @@ pub struct CursorPosition {
     _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 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;
-//             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();
-//             }
-//             Label::new(text, theme.cursor_position.clone()).into_any()
-//         } else {
-//             Empty::new().into_any()
-//         }
-//     }
-// }
-
-// 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();
-//     }
-// }
+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 {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        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))
+        })
+    }
+}
+
+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>,

crates/editor2/src/mouse_context_menu.rs πŸ”—

@@ -1,5 +1,14 @@
-use crate::{DisplayPoint, Editor, EditorMode, SelectMode};
-use gpui::{Pixels, Point, ViewContext};
+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,
@@ -7,50 +16,57 @@ pub fn deploy_context_menu(
     point: DisplayPoint,
     cx: &mut ViewContext<Editor>,
 ) {
-    todo!();
+    if !editor.is_focused(cx) {
+        editor.focus(cx);
+    }
+
+    // Don't show context menu for inline editors
+    if editor.mode() != EditorMode::Full {
+        return;
+    }
 
-    // if !editor.focused {
-    //     cx.focus_self();
-    // }
+    // Don't show the context menu if there isn't a project associated with this editor
+    if editor.project.is_none() {
+        return;
+    }
 
-    // // Don't show context menu for inline editors
-    // if editor.mode() != EditorMode::Full {
-    //     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);
+    });
 
-    // // Don't show the context menu if there isn't a project associated with this editor
-    // if editor.project.is_none() {
-    //     return;
-    // }
+    let context_menu = ui::ContextMenu::build(cx, |menu, cx| {
+        menu.action("Rename Symbol", Box::new(Rename), cx)
+            .action("Go to Definition", Box::new(GoToDefinition), cx)
+            .action("Go to Type Definition", Box::new(GoToTypeDefinition), cx)
+            .action("Find All References", Box::new(FindAllReferences), cx)
+            .action(
+                "Code Actions",
+                Box::new(ToggleCodeActions {
+                    deployed_from_indicator: false,
+                }),
+                cx,
+            )
+            .separator()
+            .action("Reveal in Finder", Box::new(RevealInFinder), cx)
+    });
+    let context_menu_focus = context_menu.focus_handle(cx);
+    cx.focus(&context_menu_focus);
 
-    // // 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 _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.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,
-    //     );
-    // });
-    // cx.notify();
+    editor.mouse_context_menu = Some(MouseContextMenu {
+        position,
+        context_menu,
+        _subscription,
+    });
+    cx.notify();
 }
 
 // #[cfg(test)]

crates/editor2/src/selections_collection.rs πŸ”—

@@ -315,14 +315,11 @@ impl SelectionsCollection {
 
         let line = display_map.layout_row(row, &text_layout_details);
 
-        dbg!("****START COL****");
         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);
-            dbg!("****END COL****");
             let end_col = line.closest_index_for_x(positions.end) as u32;
             let end = DisplayPoint::new(row, end_col);
-            dbg!(start_col, end_col);
 
             Some(Selection {
                 id: post_inc(&mut self.next_selection_id),

crates/file_finder2/src/file_finder.rs πŸ”—

@@ -15,7 +15,7 @@ use std::{
     },
 };
 use text::Point;
-use ui::{v_stack, HighlightedLabel, ListItem};
+use ui::{prelude::*, v_stack, HighlightedLabel, ListItem};
 use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
 use workspace::Workspace;
 
@@ -1256,7 +1256,7 @@ mod tests {
         //
         // 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| dbg!(cx.focused()));
+        workspace.update(cx, |_, cx| cx.focused());
 
         let initial_history = open_close_queried_buffer("fir", 1, "first.rs", &workspace, cx).await;
         assert!(

crates/gpui2/src/app/test_context.rs πŸ”—

@@ -2,8 +2,8 @@ use crate::{
     div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
     BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
     KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
-    TestPlatform, TestWindow, View, ViewContext, VisualContext, WindowContext, WindowHandle,
-    WindowOptions,
+    TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext,
+    WindowHandle, WindowOptions,
 };
 use anyhow::{anyhow, bail};
 use futures::{Stream, StreamExt};
@@ -502,6 +502,19 @@ impl<'a> VisualTestContext<'a> {
         self.cx.dispatch_action(self.window, action)
     }
 
+    pub fn window_title(&mut self) -> Option<String> {
+        self.cx
+            .update_window(self.window, |_, cx| {
+                cx.window
+                    .platform_window
+                    .as_test()
+                    .unwrap()
+                    .window_title
+                    .clone()
+            })
+            .unwrap()
+    }
+
     pub fn simulate_keystrokes(&mut self, keystrokes: &str) {
         self.cx.simulate_keystrokes(self.window, keystrokes)
     }
@@ -509,6 +522,39 @@ impl<'a> VisualTestContext<'a> {
     pub fn simulate_input(&mut self, input: &str) {
         self.cx.simulate_input(self.window, input)
     }
+
+    pub fn simulate_activation(&mut self) {
+        self.simulate_window_events(&mut |handlers| {
+            handlers
+                .active_status_change
+                .iter_mut()
+                .for_each(|f| f(true));
+        })
+    }
+
+    pub fn simulate_deactivation(&mut self) {
+        self.simulate_window_events(&mut |handlers| {
+            handlers
+                .active_status_change
+                .iter_mut()
+                .for_each(|f| f(false));
+        })
+    }
+
+    fn simulate_window_events(&mut self, f: &mut dyn FnMut(&mut TestWindowHandlers)) {
+        let handlers = self
+            .cx
+            .update_window(self.window, |_, cx| {
+                cx.window
+                    .platform_window
+                    .as_test()
+                    .unwrap()
+                    .handlers
+                    .clone()
+            })
+            .unwrap();
+        f(&mut *handlers.lock());
+    }
 }
 
 impl<'a> Context for VisualTestContext<'a> {

crates/gpui2/src/elements/text.rs πŸ”—

@@ -166,7 +166,6 @@ impl TextState {
         runs: Option<Vec<TextRun>>,
         cx: &mut WindowContext,
     ) -> LayoutId {
-        let text_system = cx.text_system().clone();
         let text_style = cx.text_style();
         let font_size = text_style.font_size.to_pixels(cx.rem_size());
         let line_height = text_style
@@ -174,18 +173,16 @@ impl TextState {
             .to_pixels(font_size.into(), cx.rem_size());
         let text = SharedString::from(text);
 
-        let rem_size = 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(), rem_size, {
+        let layout_id = cx.request_measured_layout(Default::default(), {
             let element_state = self.clone();
 
-            move |known_dimensions, available_space| {
+            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),
@@ -203,7 +200,8 @@ impl TextState {
                     }
                 }
 
-                let Some(lines) = text_system
+                let Some(lines) = cx
+                    .text_system()
                     .shape_text(
                         &text, font_size, &runs, wrap_width, // Wrap if we know the width.
                     )

crates/gpui2/src/elements/uniform_list.rs πŸ”—

@@ -109,7 +109,6 @@ impl Element for UniformList {
         cx: &mut WindowContext,
     ) -> (LayoutId, Self::State) {
         let max_items = self.item_count;
-        let rem_size = cx.rem_size();
         let item_size = state
             .as_ref()
             .map(|s| s.item_size)
@@ -120,9 +119,7 @@ impl Element for UniformList {
                 .layout(state.map(|s| s.interactive), cx, |style, cx| {
                     cx.request_measured_layout(
                         style,
-                        rem_size,
-                        move |known_dimensions: Size<Option<Pixels>>,
-                              available_space: Size<AvailableSpace>| {
+                        move |known_dimensions, available_space, _cx| {
                             let desired_height = item_size.height * max_items;
                             let width =
                                 known_dimensions

crates/gpui2/src/geometry.rs πŸ”—

@@ -655,6 +655,20 @@ pub struct Corners<T: Clone + Default + Debug> {
     pub bottom_left: T,
 }
 
+impl<T> Corners<T>
+where
+    T: Clone + Default + Debug,
+{
+    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> {
     pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
         let max = size.width.max(size.height) / 2.;

crates/gpui2/src/platform.rs πŸ”—

@@ -158,6 +158,11 @@ pub(crate) trait PlatformWindow {
     fn draw(&self, scene: Scene);
 
     fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
+
+    #[cfg(any(test, feature = "test-support"))]
+    fn as_test(&self) -> Option<&TestWindow> {
+        None
+    }
 }
 
 pub trait PlatformDispatcher: Send + Sync {

crates/gpui2/src/platform/test/platform.rs πŸ”—

@@ -189,13 +189,9 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_become_active(&self, _callback: Box<dyn FnMut()>) {}
 
-    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {
-        unimplemented!()
-    }
+    fn on_resign_active(&self, _callback: Box<dyn FnMut()>) {}
 
     fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
 

crates/gpui2/src/platform/test/window.rs πŸ”—

@@ -11,19 +11,20 @@ use std::{
 };
 
 #[derive(Default)]
-struct Handlers {
-    active_status_change: Vec<Box<dyn FnMut(bool)>>,
-    input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
-    moved: Vec<Box<dyn FnMut()>>,
-    resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
+pub(crate) struct TestWindowHandlers {
+    pub(crate) active_status_change: Vec<Box<dyn FnMut(bool)>>,
+    pub(crate) input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
+    pub(crate) moved: Vec<Box<dyn FnMut()>>,
+    pub(crate) resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
 }
 
 pub struct TestWindow {
     bounds: WindowBounds,
     current_scene: Mutex<Option<Scene>>,
     display: Rc<dyn PlatformDisplay>,
+    pub(crate) window_title: Option<String>,
     pub(crate) input_handler: Option<Arc<Mutex<Box<dyn PlatformInputHandler>>>>,
-    handlers: Mutex<Handlers>,
+    pub(crate) handlers: Arc<Mutex<TestWindowHandlers>>,
     platform: Weak<TestPlatform>,
     sprite_atlas: Arc<dyn PlatformAtlas>,
 }
@@ -42,6 +43,7 @@ impl TestWindow {
             input_handler: None,
             sprite_atlas: Arc::new(TestAtlas::new()),
             handlers: Default::default(),
+            window_title: Default::default(),
         }
     }
 }
@@ -100,8 +102,8 @@ impl PlatformWindow for TestWindow {
         todo!()
     }
 
-    fn set_title(&mut self, _title: &str) {
-        todo!()
+    fn set_title(&mut self, title: &str) {
+        self.window_title = Some(title.to_owned());
     }
 
     fn set_edited(&mut self, _edited: bool) {
@@ -167,6 +169,10 @@ impl PlatformWindow for TestWindow {
     fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
         self.sprite_atlas.clone()
     }
+
+    fn as_test(&self) -> Option<&TestWindow> {
+        Some(self)
+    }
 }
 
 pub struct TestAtlasState {

crates/gpui2/src/taffy.rs πŸ”—

@@ -1,4 +1,7 @@
-use super::{AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style};
+use crate::{
+    AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Size, Style,
+    WindowContext,
+};
 use collections::{HashMap, HashSet};
 use smallvec::SmallVec;
 use std::fmt::Debug;
@@ -9,13 +12,21 @@ use taffy::{
     Taffy,
 };
 
-type Measureable = dyn Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync;
-
 pub struct TaffyLayoutEngine {
-    taffy: Taffy<Box<Measureable>>,
+    taffy: Taffy,
     children_to_parents: HashMap<LayoutId, LayoutId>,
     absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
     computed_layouts: HashSet<LayoutId>,
+    nodes_to_measure: HashMap<
+        LayoutId,
+        Box<
+            dyn FnMut(
+                Size<Option<Pixels>>,
+                Size<AvailableSpace>,
+                &mut WindowContext,
+            ) -> Size<Pixels>,
+        >,
+    >,
 }
 
 static EXPECT_MESSAGE: &'static str =
@@ -28,6 +39,7 @@ impl TaffyLayoutEngine {
             children_to_parents: HashMap::default(),
             absolute_layout_bounds: HashMap::default(),
             computed_layouts: HashSet::default(),
+            nodes_to_measure: HashMap::default(),
         }
     }
 
@@ -36,6 +48,7 @@ impl TaffyLayoutEngine {
         self.children_to_parents.clear();
         self.absolute_layout_bounds.clear();
         self.computed_layouts.clear();
+        self.nodes_to_measure.clear();
     }
 
     pub fn request_layout(
@@ -65,18 +78,18 @@ impl TaffyLayoutEngine {
         &mut self,
         style: Style,
         rem_size: Pixels,
-        measure: impl Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels>
-            + Send
-            + Sync
+        measure: impl FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
             + 'static,
     ) -> LayoutId {
         let style = style.to_taffy(rem_size);
 
-        let measurable = Box::new(measure);
-        self.taffy
-            .new_leaf_with_context(style, measurable)
+        let layout_id = self
+            .taffy
+            .new_leaf_with_context(style, ())
             .expect(EXPECT_MESSAGE)
-            .into()
+            .into();
+        self.nodes_to_measure.insert(layout_id, Box::new(measure));
+        layout_id
     }
 
     // Used to understand performance
@@ -126,7 +139,12 @@ impl TaffyLayoutEngine {
         Ok(edges)
     }
 
-    pub fn compute_layout(&mut self, id: LayoutId, available_space: Size<AvailableSpace>) {
+    pub fn compute_layout(
+        &mut self,
+        id: LayoutId,
+        available_space: Size<AvailableSpace>,
+        cx: &mut WindowContext,
+    ) {
         // Leaving this here until we have a better instrumentation approach.
         // println!("Laying out {} children", self.count_all_children(id)?);
         // println!("Max layout depth: {}", self.max_depth(0, id)?);
@@ -159,8 +177,8 @@ impl TaffyLayoutEngine {
             .compute_layout_with_measure(
                 id.into(),
                 available_space.into(),
-                |known_dimensions, available_space, _node_id, context| {
-                    let Some(measure) = context else {
+                |known_dimensions, available_space, node_id, _context| {
+                    let Some(measure) = self.nodes_to_measure.get_mut(&node_id.into()) else {
                         return taffy::geometry::Size::default();
                     };
 
@@ -169,10 +187,11 @@ impl TaffyLayoutEngine {
                         height: known_dimensions.height.map(Pixels),
                     };
 
-                    measure(known_dimensions, available_space.into()).into()
+                    measure(known_dimensions, available_space.into(), cx).into()
                 },
             )
             .expect(EXPECT_MESSAGE);
+
         // println!("compute_layout took {:?}", started_at.elapsed());
     }
 

crates/gpui2/src/view.rs πŸ”—

@@ -209,9 +209,7 @@ impl AnyView {
     ) {
         cx.with_absolute_element_offset(origin, |cx| {
             let (layout_id, rendered_element) = (self.layout)(self, cx);
-            cx.window
-                .layout_engine
-                .compute_layout(layout_id, available_space);
+            cx.compute_layout(layout_id, available_space);
             (self.paint)(self, rendered_element, cx);
         })
     }
@@ -240,6 +238,10 @@ impl Element for AnyView {
     }
 
     fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        debug_assert!(
+            state.is_some(),
+            "state is None. Did you include an AnyView twice in the tree?"
+        );
         (self.paint)(&self, state.take().unwrap(), cx)
     }
 }

crates/gpui2/src/window.rs πŸ”—

@@ -209,7 +209,7 @@ pub struct Window {
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
     viewport_size: Size<Pixels>,
-    pub(crate) layout_engine: TaffyLayoutEngine,
+    layout_engine: Option<TaffyLayoutEngine>,
     pub(crate) root_view: Option<AnyView>,
     pub(crate) element_id_stack: GlobalElementId,
     pub(crate) previous_frame: Frame,
@@ -327,7 +327,7 @@ impl Window {
             sprite_atlas,
             rem_size: px(16.),
             viewport_size: content_size,
-            layout_engine: TaffyLayoutEngine::new(),
+            layout_engine: Some(TaffyLayoutEngine::new()),
             root_view: None,
             element_id_stack: GlobalElementId::default(),
             previous_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
@@ -606,9 +606,11 @@ impl<'a> WindowContext<'a> {
         self.app.layout_id_buffer.extend(children.into_iter());
         let rem_size = self.rem_size();
 
-        self.window
-            .layout_engine
-            .request_layout(style, rem_size, &self.app.layout_id_buffer)
+        self.window.layout_engine.as_mut().unwrap().request_layout(
+            style,
+            rem_size,
+            &self.app.layout_id_buffer,
+        )
     }
 
     /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
@@ -618,22 +620,25 @@ impl<'a> WindowContext<'a> {
     /// The given closure is invoked at layout time with the known dimensions and available space and
     /// returns a `Size`.
     pub fn request_measured_layout<
-        F: Fn(Size<Option<Pixels>>, Size<AvailableSpace>) -> Size<Pixels> + Send + Sync + 'static,
+        F: FnMut(Size<Option<Pixels>>, Size<AvailableSpace>, &mut WindowContext) -> Size<Pixels>
+            + 'static,
     >(
         &mut self,
         style: Style,
-        rem_size: Pixels,
         measure: F,
     ) -> LayoutId {
+        let rem_size = self.rem_size();
         self.window
             .layout_engine
+            .as_mut()
+            .unwrap()
             .request_measured_layout(style, rem_size, measure)
     }
 
     pub fn compute_layout(&mut self, layout_id: LayoutId, available_space: Size<AvailableSpace>) {
-        self.window
-            .layout_engine
-            .compute_layout(layout_id, available_space)
+        let mut layout_engine = self.window.layout_engine.take().unwrap();
+        layout_engine.compute_layout(layout_id, available_space, self);
+        self.window.layout_engine = Some(layout_engine);
     }
 
     /// Obtain the bounds computed for the given LayoutId relative to the window. This method should not
@@ -643,6 +648,8 @@ impl<'a> WindowContext<'a> {
         let mut bounds = self
             .window
             .layout_engine
+            .as_mut()
+            .unwrap()
             .layout_bounds(layout_id)
             .map(Into::into);
         bounds.origin += self.element_offset();
@@ -678,6 +685,10 @@ impl<'a> WindowContext<'a> {
         self.window.platform_window.zoom();
     }
 
+    pub fn set_window_title(&mut self, title: &str) {
+        self.window.platform_window.set_title(title);
+    }
+
     pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
         self.platform
             .displays()
@@ -1189,7 +1200,7 @@ impl<'a> WindowContext<'a> {
         self.text_system().start_frame();
 
         let window = &mut *self.window;
-        window.layout_engine.clear();
+        window.layout_engine.as_mut().unwrap().clear();
 
         mem::swap(&mut window.previous_frame, &mut window.current_frame);
         let frame = &mut window.current_frame;

crates/project_panel/src/project_panel.rs πŸ”—

@@ -1627,9 +1627,21 @@ impl View for ProjectPanel {
         }
     }
 
-    fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
+    fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
         Self::reset_to_default_keymap_context(keymap);
         keymap.add_identifier("menu");
+
+        if let Some(window) = cx.active_window() {
+            window.read_with(cx, |cx| {
+                let identifier = if self.filename_editor.is_focused(cx) {
+                    "editing"
+                } else {
+                    "not_editing"
+                };
+
+                keymap.add_identifier(identifier);
+            });
+        }
     }
 
     fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {

crates/project_panel2/src/project_panel.rs πŸ”—

@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
 use gpui::{
     actions, div, overlay, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
     ClipboardItem, DismissEvent, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
-    InteractiveElement, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point,
-    PromptLevel, Render, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View,
-    ViewContext, VisualContext as _, WeakView, WindowContext,
+    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::{
@@ -29,8 +29,7 @@ use std::{
     path::Path,
     sync::Arc,
 };
-use theme::ActiveTheme as _;
-use ui::{v_stack, ContextMenu, IconElement, Label, ListItem};
+use ui::{prelude::*, v_stack, ContextMenu, IconElement, Label, ListItem};
 use unicase::UniCase;
 use util::{maybe, ResultExt, TryFutureExt};
 use workspace::{
@@ -1421,6 +1420,22 @@ impl ProjectPanel {
         //     );
         // })
     }
+
+    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
+    }
 }
 
 impl Render for ProjectPanel {
@@ -1434,7 +1449,7 @@ impl Render for ProjectPanel {
                 .id("project-panel")
                 .size_full()
                 .relative()
-                .key_context("ProjectPanel")
+                .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))
@@ -2845,7 +2860,7 @@ mod tests {
                 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(Selection {
+                    panel.selection = Some(crate::Selection {
                         worktree_id: worktree.id(),
                         entry_id,
                     });

crates/search2/src/search.rs πŸ”—

@@ -4,7 +4,7 @@ use gpui::{actions, Action, AppContext, IntoElement};
 pub use mode::SearchMode;
 use project::search::SearchQuery;
 use ui::prelude::*;
-use ui::{ButtonStyle2, Icon, IconButton};
+use ui::{ButtonStyle, Icon, IconButton};
 //pub use project_search::{ProjectSearchBar, ProjectSearchView};
 // use theme::components::{
 //     action_button::Button, svg::Svg, ComponentExt, IconButtonStyle, ToggleIconButtonStyle,
@@ -91,8 +91,8 @@ impl SearchOptions {
                     cx.dispatch_action(action.boxed_clone());
                 }
             })
-            .style(ButtonStyle2::Subtle)
-            .when(active, |button| button.style(ButtonStyle2::Filled))
+            .style(ButtonStyle::Subtle)
+            .when(active, |button| button.style(ButtonStyle::Filled))
     }
 }
 
@@ -103,8 +103,8 @@ fn toggle_replace_button(active: bool) -> impl IntoElement {
             cx.dispatch_action(Box::new(ToggleReplace));
             cx.notify();
         })
-        .style(ButtonStyle2::Subtle)
-        .when(active, |button| button.style(ButtonStyle2::Filled))
+        .style(ButtonStyle::Subtle)
+        .when(active, |button| button.style(ButtonStyle::Filled))
 }
 
 fn render_replace_button(

crates/storybook2/src/stories.rs πŸ”—

@@ -1,3 +1,4 @@
+mod auto_height_editor;
 mod focus;
 mod kitchen_sink;
 mod picker;
@@ -5,6 +6,7 @@ mod scroll;
 mod text;
 mod z_index;
 
+pub use auto_height_editor::*;
 pub use focus::*;
 pub use kitchen_sink::*;
 pub use picker::*;

crates/storybook2/src/stories/auto_height_editor.rs πŸ”—

@@ -0,0 +1,34 @@
+use editor::Editor;
+use gpui::{
+    div, white, Div, KeyBinding, ParentElement, Render, Styled, View, ViewContext, VisualContext,
+    WindowContext,
+};
+
+pub struct AutoHeightEditorStory {
+    editor: View<Editor>,
+}
+
+impl AutoHeightEditorStory {
+    pub fn new(cx: &mut WindowContext) -> View<Self> {
+        cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
+        cx.build_view(|cx| Self {
+            editor: cx.build_view(|cx| {
+                let mut editor = Editor::auto_height(3, cx);
+                editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+                editor
+            }),
+        })
+    }
+}
+
+impl Render for AutoHeightEditorStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        div()
+            .size_full()
+            .bg(white())
+            .text_sm()
+            .child(div().w_32().bg(gpui::black()).child(self.editor.clone()))
+    }
+}

crates/storybook2/src/story_selector.rs πŸ”—

@@ -12,6 +12,7 @@ use ui::prelude::*;
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 pub enum ComponentStory {
+    AutoHeightEditor,
     Avatar,
     Button,
     Checkbox,
@@ -23,6 +24,7 @@ pub enum ComponentStory {
     Keybinding,
     Label,
     List,
+    ListHeader,
     ListItem,
     Scroll,
     Text,
@@ -33,6 +35,7 @@ pub enum ComponentStory {
 impl ComponentStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
+            Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
             Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(),
             Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
             Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
@@ -44,6 +47,7 @@ impl ComponentStory {
             Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
             Self::Label => cx.build_view(|_| ui::LabelStory).into(),
             Self::List => cx.build_view(|_| ui::ListStory).into(),
+            Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
             Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
             Self::Scroll => ScrollStory::view(cx).into(),
             Self::Text => TextStory::view(cx).into(),

crates/theme_selector2/src/theme_selector.rs πŸ”—

@@ -2,14 +2,14 @@ use feature_flags::FeatureFlagAppExt;
 use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render,
-    SharedString, View, ViewContext, VisualContext, WeakView,
+    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, SharedString, View,
+    ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use settings::{update_settings_file, SettingsStore};
 use std::sync::Arc;
-use theme::{ActiveTheme, Theme, ThemeRegistry, ThemeSettings};
-use ui::ListItem;
+use theme::{Theme, ThemeRegistry, ThemeSettings};
+use ui::{prelude::*, ListItem};
 use util::ResultExt;
 use workspace::{ui::HighlightedLabel, Workspace};
 

crates/ui2/src/components/button/button.rs πŸ”—

@@ -1,7 +1,7 @@
 use gpui::AnyView;
 
 use crate::prelude::*;
-use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Label, LineHeightStyle};
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Label, LineHeightStyle};
 
 #[derive(IntoElement)]
 pub struct Button {
@@ -54,12 +54,12 @@ impl ButtonCommon for Button {
         self.base.id()
     }
 
-    fn style(mut self, style: ButtonStyle2) -> Self {
+    fn style(mut self, style: ButtonStyle) -> Self {
         self.base = self.base.style(style);
         self
     }
 
-    fn size(mut self, size: ButtonSize2) -> Self {
+    fn size(mut self, size: ButtonSize) -> Self {
         self.base = self.base.size(size);
         self
     }
@@ -79,7 +79,7 @@ impl RenderOnce for Button {
         } else if self.base.selected {
             Color::Selected
         } else {
-            Color::Default
+            self.label_color.unwrap_or_default()
         };
 
         self.base.child(

crates/ui2/src/components/button/button_like.rs πŸ”—

@@ -1,4 +1,4 @@
-use gpui::{rems, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
+use gpui::{rems, transparent_black, AnyElement, AnyView, ClickEvent, Div, Hsla, Rems, Stateful};
 use smallvec::SmallVec;
 
 use crate::h_stack;
@@ -6,13 +6,13 @@ use crate::prelude::*;
 
 pub trait ButtonCommon: Clickable + Disableable {
     fn id(&self) -> &ElementId;
-    fn style(self, style: ButtonStyle2) -> Self;
-    fn size(self, size: ButtonSize2) -> Self;
+    fn style(self, style: ButtonStyle) -> Self;
+    fn size(self, size: ButtonSize) -> Self;
     fn tooltip(self, tooltip: impl Fn(&mut WindowContext) -> AnyView + 'static) -> Self;
 }
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
-pub enum ButtonStyle2 {
+pub enum ButtonStyle {
     #[default]
     Filled,
     // Tinted,
@@ -21,54 +21,57 @@ pub enum ButtonStyle2 {
 }
 
 #[derive(Debug, Clone)]
-pub struct ButtonStyle {
+pub(crate) struct ButtonLikeStyles {
     pub background: Hsla,
+    #[allow(unused)]
     pub border_color: Hsla,
+    #[allow(unused)]
     pub label_color: Hsla,
+    #[allow(unused)]
     pub icon_color: Hsla,
 }
 
-impl ButtonStyle2 {
-    pub fn enabled(self, cx: &mut WindowContext) -> ButtonStyle {
+impl ButtonStyle {
+    pub(crate) fn enabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
         match self {
-            ButtonStyle2::Filled => ButtonStyle {
+            ButtonStyle::Filled => ButtonLikeStyles {
                 background: cx.theme().colors().element_background,
-                border_color: gpui::transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Subtle => ButtonStyle {
+            ButtonStyle::Subtle => ButtonLikeStyles {
                 background: cx.theme().colors().ghost_element_background,
-                border_color: gpui::transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
         }
     }
 
-    pub fn hovered(self, cx: &mut WindowContext) -> ButtonStyle {
+    pub(crate) fn hovered(self, cx: &mut WindowContext) -> ButtonLikeStyles {
         match self {
-            ButtonStyle2::Filled => ButtonStyle {
+            ButtonStyle::Filled => ButtonLikeStyles {
                 background: cx.theme().colors().element_hover,
-                border_color: gpui::transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Subtle => ButtonStyle {
+            ButtonStyle::Subtle => ButtonLikeStyles {
                 background: cx.theme().colors().ghost_element_hover,
-                border_color: gpui::transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
                 // TODO: These are not great
                 label_color: Color::Muted.color(cx),
                 // TODO: These are not great
@@ -77,23 +80,23 @@ impl ButtonStyle2 {
         }
     }
 
-    pub fn active(self, cx: &mut WindowContext) -> ButtonStyle {
+    pub(crate) fn active(self, cx: &mut WindowContext) -> ButtonLikeStyles {
         match self {
-            ButtonStyle2::Filled => ButtonStyle {
+            ButtonStyle::Filled => ButtonLikeStyles {
                 background: cx.theme().colors().element_active,
-                border_color: gpui::transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Subtle => ButtonStyle {
+            ButtonStyle::Subtle => ButtonLikeStyles {
                 background: cx.theme().colors().ghost_element_active,
-                border_color: gpui::transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
                 // TODO: These are not great
                 label_color: Color::Muted.color(cx),
                 // TODO: These are not great
@@ -102,22 +105,23 @@ impl ButtonStyle2 {
         }
     }
 
-    pub fn focused(self, cx: &mut WindowContext) -> ButtonStyle {
+    #[allow(unused)]
+    pub(crate) fn focused(self, cx: &mut WindowContext) -> ButtonLikeStyles {
         match self {
-            ButtonStyle2::Filled => ButtonStyle {
+            ButtonStyle::Filled => ButtonLikeStyles {
                 background: cx.theme().colors().element_background,
                 border_color: cx.theme().colors().border_focused,
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Subtle => ButtonStyle {
+            ButtonStyle::Subtle => ButtonLikeStyles {
                 background: cx.theme().colors().ghost_element_background,
                 border_color: cx.theme().colors().border_focused,
                 label_color: Color::Default.color(cx),
                 icon_color: Color::Default.color(cx),
             },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
                 border_color: cx.theme().colors().border_focused,
                 label_color: Color::Accent.color(cx),
                 icon_color: Color::Accent.color(cx),
@@ -125,23 +129,23 @@ impl ButtonStyle2 {
         }
     }
 
-    pub fn disabled(self, cx: &mut WindowContext) -> ButtonStyle {
+    pub(crate) fn disabled(self, cx: &mut WindowContext) -> ButtonLikeStyles {
         match self {
-            ButtonStyle2::Filled => ButtonStyle {
+            ButtonStyle::Filled => ButtonLikeStyles {
                 background: cx.theme().colors().element_disabled,
                 border_color: cx.theme().colors().border_disabled,
                 label_color: Color::Disabled.color(cx),
                 icon_color: Color::Disabled.color(cx),
             },
-            ButtonStyle2::Subtle => ButtonStyle {
+            ButtonStyle::Subtle => ButtonLikeStyles {
                 background: cx.theme().colors().ghost_element_disabled,
                 border_color: cx.theme().colors().border_disabled,
                 label_color: Color::Disabled.color(cx),
                 icon_color: Color::Disabled.color(cx),
             },
-            ButtonStyle2::Transparent => ButtonStyle {
-                background: gpui::transparent_black(),
-                border_color: gpui::transparent_black(),
+            ButtonStyle::Transparent => ButtonLikeStyles {
+                background: transparent_black(),
+                border_color: transparent_black(),
                 label_color: Color::Disabled.color(cx),
                 icon_color: Color::Disabled.color(cx),
             },
@@ -150,19 +154,19 @@ impl ButtonStyle2 {
 }
 
 #[derive(Default, PartialEq, Clone, Copy)]
-pub enum ButtonSize2 {
+pub enum ButtonSize {
     #[default]
     Default,
     Compact,
     None,
 }
 
-impl ButtonSize2 {
+impl ButtonSize {
     fn height(self) -> Rems {
         match self {
-            ButtonSize2::Default => rems(22. / 16.),
-            ButtonSize2::Compact => rems(18. / 16.),
-            ButtonSize2::None => rems(16. / 16.),
+            ButtonSize::Default => rems(22. / 16.),
+            ButtonSize::Compact => rems(18. / 16.),
+            ButtonSize::None => rems(16. / 16.),
         }
     }
 }
@@ -170,10 +174,10 @@ impl ButtonSize2 {
 #[derive(IntoElement)]
 pub struct ButtonLike {
     id: ElementId,
-    pub(super) style: ButtonStyle2,
+    pub(super) style: ButtonStyle,
     pub(super) disabled: bool,
     pub(super) selected: bool,
-    size: ButtonSize2,
+    size: ButtonSize,
     tooltip: Option<Box<dyn Fn(&mut WindowContext) -> AnyView>>,
     on_click: Option<Box<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
     children: SmallVec<[AnyElement; 2]>,
@@ -183,10 +187,10 @@ impl ButtonLike {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
             id: id.into(),
-            style: ButtonStyle2::default(),
+            style: ButtonStyle::default(),
             disabled: false,
             selected: false,
-            size: ButtonSize2::Default,
+            size: ButtonSize::Default,
             tooltip: None,
             children: SmallVec::new(),
             on_click: None,
@@ -220,12 +224,12 @@ impl ButtonCommon for ButtonLike {
         &self.id
     }
 
-    fn style(mut self, style: ButtonStyle2) -> Self {
+    fn style(mut self, style: ButtonStyle) -> Self {
         self.style = style;
         self
     }
 
-    fn size(mut self, size: ButtonSize2) -> Self {
+    fn size(mut self, size: ButtonSize) -> Self {
         self.size = size;
         self
     }

crates/ui2/src/components/button/icon_button.rs πŸ”—

@@ -1,7 +1,7 @@
 use gpui::{Action, AnyView};
 
 use crate::prelude::*;
-use crate::{ButtonCommon, ButtonLike, ButtonSize2, ButtonStyle2, Icon, IconElement, IconSize};
+use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, Icon, IconElement, IconSize};
 
 #[derive(IntoElement)]
 pub struct IconButton {
@@ -65,12 +65,12 @@ impl ButtonCommon for IconButton {
         self.base.id()
     }
 
-    fn style(mut self, style: ButtonStyle2) -> Self {
+    fn style(mut self, style: ButtonStyle) -> Self {
         self.base = self.base.style(style);
         self
     }
 
-    fn size(mut self, size: ButtonSize2) -> Self {
+    fn size(mut self, size: ButtonSize) -> Self {
         self.base = self.base.size(size);
         self
     }

crates/ui2/src/components/list.rs πŸ”—

@@ -1,73 +1,11 @@
+mod list;
 mod list_header;
 mod list_item;
 mod list_separator;
 mod list_sub_header;
 
-use gpui::{AnyElement, Div};
-use smallvec::SmallVec;
-
-use crate::prelude::*;
-use crate::{v_stack, Label};
-
+pub use list::*;
 pub use list_header::*;
 pub use list_item::*;
 pub use list_separator::*;
 pub use list_sub_header::*;
-
-#[derive(IntoElement)]
-pub struct List {
-    /// Message to display when the list is empty
-    /// Defaults to "No items"
-    empty_message: SharedString,
-    header: Option<ListHeader>,
-    toggle: Option<bool>,
-    children: SmallVec<[AnyElement; 2]>,
-}
-
-impl List {
-    pub fn new() -> Self {
-        Self {
-            empty_message: "No items".into(),
-            header: None,
-            toggle: None,
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
-        self.empty_message = empty_message.into();
-        self
-    }
-
-    pub fn header(mut self, header: ListHeader) -> Self {
-        self.header = Some(header);
-        self
-    }
-
-    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
-        self.toggle = toggle.into();
-        self
-    }
-}
-
-impl ParentElement for List {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
-        &mut self.children
-    }
-}
-
-impl RenderOnce for List {
-    type Rendered = Div;
-
-    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
-        v_stack()
-            .w_full()
-            .py_1()
-            .children(self.header.map(|header| header))
-            .map(|this| match (self.children.is_empty(), self.toggle) {
-                (false, _) => this.children(self.children),
-                (true, Some(false)) => this,
-                (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
-            })
-    }
-}

crates/ui2/src/components/list/list.rs πŸ”—

@@ -0,0 +1,60 @@
+use gpui::{AnyElement, Div};
+use smallvec::SmallVec;
+
+use crate::{prelude::*, v_stack, Label, ListHeader};
+
+#[derive(IntoElement)]
+pub struct List {
+    /// Message to display when the list is empty
+    /// Defaults to "No items"
+    empty_message: SharedString,
+    header: Option<ListHeader>,
+    toggle: Option<bool>,
+    children: SmallVec<[AnyElement; 2]>,
+}
+
+impl List {
+    pub fn new() -> Self {
+        Self {
+            empty_message: "No items".into(),
+            header: None,
+            toggle: None,
+            children: SmallVec::new(),
+        }
+    }
+
+    pub fn empty_message(mut self, empty_message: impl Into<SharedString>) -> Self {
+        self.empty_message = empty_message.into();
+        self
+    }
+
+    pub fn header(mut self, header: impl Into<Option<ListHeader>>) -> Self {
+        self.header = header.into();
+        self
+    }
+
+    pub fn toggle(mut self, toggle: impl Into<Option<bool>>) -> Self {
+        self.toggle = toggle.into();
+        self
+    }
+}
+
+impl ParentElement for List {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
+        &mut self.children
+    }
+}
+
+impl RenderOnce for List {
+    type Rendered = Div;
+
+    fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
+        v_stack().w_full().py_1().children(self.header).map(|this| {
+            match (self.children.is_empty(), self.toggle) {
+                (false, _) => this.children(self.children),
+                (true, Some(false)) => this,
+                (true, _) => this.child(Label::new(self.empty_message.clone()).color(Color::Muted)),
+            }
+        })
+    }
+}

crates/ui2/src/components/list/list_header.rs πŸ”—

@@ -1,22 +1,16 @@
 use std::rc::Rc;
 
-use gpui::{ClickEvent, Div};
+use gpui::{AnyElement, ClickEvent, Div};
+use smallvec::SmallVec;
 
 use crate::prelude::*;
-use crate::{h_stack, Disclosure, Icon, IconButton, IconElement, IconSize, Label};
-
-pub enum ListHeaderMeta {
-    Tools(Vec<IconButton>),
-    // TODO: This should be a button
-    Button(Label),
-    Text(Label),
-}
+use crate::{h_stack, Disclosure, Icon, IconElement, IconSize, Label};
 
 #[derive(IntoElement)]
 pub struct ListHeader {
     label: SharedString,
     left_icon: Option<Icon>,
-    meta: Option<ListHeaderMeta>,
+    meta: SmallVec<[AnyElement; 2]>,
     toggle: Option<bool>,
     on_toggle: Option<Rc<dyn Fn(&ClickEvent, &mut WindowContext) + 'static>>,
     inset: bool,
@@ -28,7 +22,7 @@ impl ListHeader {
         Self {
             label: label.into(),
             left_icon: None,
-            meta: None,
+            meta: SmallVec::new(),
             inset: false,
             toggle: None,
             on_toggle: None,
@@ -49,21 +43,19 @@ impl ListHeader {
         self
     }
 
-    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
-        self.left_icon = left_icon;
+    pub fn left_icon(mut self, left_icon: impl Into<Option<Icon>>) -> Self {
+        self.left_icon = left_icon.into();
         self
     }
 
-    pub fn right_button(self, button: IconButton) -> Self {
-        self.meta(Some(ListHeaderMeta::Tools(vec![button])))
-    }
-
-    pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
-        self.meta = meta;
+    pub fn meta(mut self, meta: impl IntoElement) -> Self {
+        self.meta.push(meta.into_any_element());
         self
     }
+}
 
-    pub fn selected(mut self, selected: bool) -> Self {
+impl Selectable for ListHeader {
+    fn selected(mut self, selected: bool) -> Self {
         self.selected = selected;
         self
     }
@@ -73,18 +65,6 @@ impl RenderOnce for ListHeader {
     type Rendered = Div;
 
     fn render(self, cx: &mut WindowContext) -> Self::Rendered {
-        let meta = match self.meta {
-            Some(ListHeaderMeta::Tools(icons)) => div().child(
-                h_stack()
-                    .gap_2()
-                    .items_center()
-                    .children(icons.into_iter().map(|i| i.icon_color(Color::Muted))),
-            ),
-            Some(ListHeaderMeta::Button(label)) => div().child(label),
-            Some(ListHeaderMeta::Text(label)) => div().child(label),
-            None => div(),
-        };
-
         h_stack().w_full().relative().child(
             div()
                 .h_5()
@@ -118,7 +98,7 @@ impl RenderOnce for ListHeader {
                                 .map(|is_open| Disclosure::new(is_open).on_toggle(self.on_toggle)),
                         ),
                 )
-                .child(meta),
+                .child(h_stack().gap_2().items_center().children(self.meta)),
         )
     }
 }

crates/ui2/src/components/list/list_item.rs πŸ”—

@@ -83,11 +83,6 @@ impl ListItem {
         self
     }
 
-    pub fn selected(mut self, selected: bool) -> Self {
-        self.selected = selected;
-        self
-    }
-
     pub fn left_child(mut self, left_content: impl IntoElement) -> Self {
         self.left_slot = Some(left_content.into_any_element());
         self
@@ -109,6 +104,13 @@ impl ListItem {
     }
 }
 
+impl Selectable for ListItem {
+    fn selected(mut self, selected: bool) -> Self {
+        self.selected = selected;
+        self
+    }
+}
+
 impl ParentElement for ListItem {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
         &mut self.children

crates/ui2/src/components/stories.rs πŸ”—

@@ -8,6 +8,7 @@ mod icon_button;
 mod keybinding;
 mod label;
 mod list;
+mod list_header;
 mod list_item;
 
 pub use avatar::*;
@@ -20,4 +21,5 @@ pub use icon_button::*;
 pub use keybinding::*;
 pub use label::*;
 pub use list::*;
+pub use list_header::*;
 pub use list_item::*;

crates/ui2/src/components/stories/button.rs πŸ”—

@@ -2,7 +2,7 @@ use gpui::{Div, Render};
 use story::Story;
 
 use crate::prelude::*;
-use crate::{Button, ButtonStyle2};
+use crate::{Button, ButtonStyle};
 
 pub struct ButtonStory;
 
@@ -14,9 +14,13 @@ impl Render for ButtonStory {
             .child(Story::title_for::<Button>())
             .child(Story::label("Default"))
             .child(Button::new("default_filled", "Click me"))
+            .child(Story::label("Selected"))
+            .child(Button::new("selected_filled", "Click me").selected(true))
+            .child(Story::label("With `label_color`"))
+            .child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
             .child(Story::label("Default (Subtle)"))
-            .child(Button::new("default_subtle", "Click me").style(ButtonStyle2::Subtle))
+            .child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
             .child(Story::label("Default (Transparent)"))
-            .child(Button::new("default_transparent", "Click me").style(ButtonStyle2::Transparent))
+            .child(Button::new("default_transparent", "Click me").style(ButtonStyle::Transparent))
     }
 }

crates/ui2/src/components/stories/list.rs πŸ”—

@@ -22,12 +22,12 @@ impl Render for ListStory {
             .child(Story::label("With sections"))
             .child(
                 List::new()
-                    .child(ListHeader::new("Fruits"))
+                    .header(ListHeader::new("Produce"))
+                    .child(ListSubHeader::new("Fruits"))
                     .child(ListItem::new("apple").child("Apple"))
                     .child(ListItem::new("banana").child("Banana"))
                     .child(ListItem::new("cherry").child("Cherry"))
                     .child(ListSeparator)
-                    .child(ListHeader::new("Vegetables"))
                     .child(ListSubHeader::new("Root Vegetables"))
                     .child(ListItem::new("carrot").child("Carrot"))
                     .child(ListItem::new("potato").child("Potato"))

crates/ui2/src/components/stories/list_header.rs πŸ”—

@@ -0,0 +1,33 @@
+use gpui::{Div, Render};
+use story::Story;
+
+use crate::{prelude::*, IconButton};
+use crate::{Icon, ListHeader};
+
+pub struct ListHeaderStory;
+
+impl Render for ListHeaderStory {
+    type Element = Div;
+
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+        Story::container()
+            .child(Story::title_for::<ListHeader>())
+            .child(Story::label("Default"))
+            .child(ListHeader::new("Section 1"))
+            .child(Story::label("With left icon"))
+            .child(ListHeader::new("Section 2").left_icon(Icon::Bell))
+            .child(Story::label("With left icon and meta"))
+            .child(
+                ListHeader::new("Section 3")
+                    .left_icon(Icon::BellOff)
+                    .meta(IconButton::new("action_1", Icon::Bolt)),
+            )
+            .child(Story::label("With multiple meta"))
+            .child(
+                ListHeader::new("Section 4")
+                    .meta(IconButton::new("action_1", Icon::Bolt))
+                    .meta(IconButton::new("action_2", Icon::ExclamationTriangle))
+                    .meta(IconButton::new("action_3", Icon::Plus)),
+            )
+    }
+}

crates/ui2/src/slot.rs πŸ”—

@@ -1,12 +0,0 @@
-use gpui::{ImageSource, SharedString};
-
-use crate::Icon;
-
-/// A slot utility that provides a way to to pass either
-/// an icon or an image to a component.
-#[derive(Debug, Clone)]
-pub enum GraphicSlot {
-    Icon(Icon),
-    Avatar(ImageSource),
-    PublicActor(SharedString),
-}

crates/ui2/src/ui2.rs πŸ”—

@@ -18,7 +18,6 @@ mod disableable;
 mod fixed;
 pub mod prelude;
 mod selectable;
-mod slot;
 mod styled_ext;
 mod styles;
 pub mod utils;
@@ -29,6 +28,5 @@ pub use disableable::*;
 pub use fixed::*;
 pub use prelude::*;
 pub use selectable::*;
-pub use slot::*;
 pub use styled_ext::*;
 pub use styles::*;

crates/welcome2/src/base_keymap_picker.rs πŸ”—

@@ -1,14 +1,14 @@
 use super::base_keymap_setting::BaseKeymap;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, AppContext, DismissEvent, EventEmitter, FocusableView, ParentElement, Render, Task,
-    View, ViewContext, VisualContext, WeakView,
+    actions, AppContext, DismissEvent, EventEmitter, FocusableView, Render, Task, View,
+    ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use project::Fs;
 use settings::{update_settings_file, Settings};
 use std::sync::Arc;
-use ui::ListItem;
+use ui::{prelude::*, ListItem};
 use util::ResultExt;
 use workspace::{ui::HighlightedLabel, Workspace};
 

crates/workspace2/Cargo.toml πŸ”—

@@ -10,29 +10,29 @@ doctest = false
 
 [features]
 test-support = [
-    "call2/test-support",
-    "client2/test-support",
-    "project2/test-support",
-    "settings2/test-support",
+    "call/test-support",
+    "client/test-support",
+    "project/test-support",
+    "settings/test-support",
     "gpui/test-support",
-    "fs2/test-support"
+    "fs/test-support"
 ]
 
 [dependencies]
-db2 = { path = "../db2" }
-client2 = { path = "../client2" }
+db = { path = "../db2", package = "db2" }
+client = { path = "../client2", package = "client2" }
 collections = { path = "../collections" }
 # context_menu = { path = "../context_menu" }
-fs2 = { path = "../fs2" }
+fs = { path = "../fs2", package = "fs2" }
 gpui = { package = "gpui2", path = "../gpui2" }
-install_cli2 = { path = "../install_cli2" }
-language2 = { path = "../language2" }
+install_cli = { path = "../install_cli2", package = "install_cli2" }
+language = { path = "../language2", package = "language2" }
 #menu = { path = "../menu" }
 node_runtime = { path = "../node_runtime" }
-project2 = { path = "../project2" }
-settings2 = { path = "../settings2" }
-terminal2 = { path = "../terminal2" }
-theme2 = { path = "../theme2" }
+project = { path = "../project2", package = "project2" }
+settings = { path = "../settings2", package = "settings2" }
+terminal = { path = "../terminal2", package = "terminal2" }
+theme = { path = "../theme2", package = "theme2" }
 util = { path = "../util" }
 ui = { package = "ui2", path = "../ui2" }
 
@@ -54,13 +54,13 @@ smallvec.workspace = true
 uuid.workspace = true
 
 [dev-dependencies]
-call2 = { path = "../call2", features = ["test-support"] }
-client2 = { path = "../client2", features = ["test-support"] }
-gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-project2 = { path = "../project2", features = ["test-support"] }
-settings2 = { path = "../settings2", features = ["test-support"] }
-fs2 = { path = "../fs2", features = ["test-support"] }
-db2 = { path = "../db2", features = ["test-support"] }
+call = { path = "../call2", package = "call2", features = ["test-support"] }
+client = { path = "../client2", package = "client2", features = ["test-support"] }
+gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
+project = { path = "../project2", package = "project2", features = ["test-support"] }
+settings = { path = "../settings2", package = "settings2", features = ["test-support"] }
+fs = { path = "../fs2", package = "fs2", features = ["test-support"] }
+db = { path = "../db2", package = "db2", features = ["test-support"] }
 
 indoc.workspace = true
 env_logger.workspace = true

crates/workspace2/src/item.rs πŸ”—

@@ -7,7 +7,7 @@ use crate::{
     ViewId, Workspace, WorkspaceId,
 };
 use anyhow::Result;
-use client2::{
+use client::{
     proto::{self, PeerId},
     Client,
 };
@@ -16,10 +16,10 @@ use gpui::{
     HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
     WindowContext,
 };
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings2::Settings;
+use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -33,7 +33,7 @@ use std::{
     },
     time::Duration,
 };
-use theme2::Theme;
+use theme::Theme;
 
 #[derive(Deserialize)]
 pub struct ItemSettings {
@@ -110,7 +110,7 @@ pub trait Item: FocusableView + EventEmitter<ItemEvent> {
     fn for_each_project_item(
         &self,
         _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
     ) {
     }
     fn is_singleton(&self, _cx: &AppContext) -> bool {
@@ -222,7 +222,7 @@ pub trait ItemHandle: 'static + Send {
     fn for_each_project_item(
         &self,
         _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
     );
     fn is_singleton(&self, cx: &AppContext) -> bool;
     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
@@ -347,7 +347,7 @@ impl<T: Item> ItemHandle for View<T> {
     fn for_each_project_item(
         &self,
         cx: &AppContext,
-        f: &mut dyn FnMut(EntityId, &dyn project2::Item),
+        f: &mut dyn FnMut(EntityId, &dyn project::Item),
     ) {
         self.read(cx).for_each_project_item(cx, f)
     }
@@ -375,6 +375,7 @@ impl<T: Item> ItemHandle for View<T> {
         pane: View<Pane>,
         cx: &mut ViewContext<Workspace>,
     ) {
+        let weak_item = self.downgrade();
         let history = pane.read(cx).nav_history_for_item(self);
         self.update(cx, |this, cx| {
             this.set_nav_history(history, cx);
@@ -491,16 +492,15 @@ impl<T: Item> ItemHandle for View<T> {
                     }
                 }));
 
-            // todo!()
-            // cx.observe_focus(self, move |workspace, item, focused, cx| {
-            //     if !focused
-            //         && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
-            //     {
-            //         Pane::autosave_item(&item, workspace.project.clone(), cx)
-            //             .detach_and_log_err(cx);
-            //     }
-            // })
-            // .detach();
+            cx.on_blur(&self.focus_handle(cx), move |workspace, cx| {
+                if WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange {
+                    if let Some(item) = weak_item.upgrade() {
+                        Pane::autosave_item(&item, workspace.project.clone(), cx)
+                            .detach_and_log_err(cx);
+                    }
+                }
+            })
+            .detach();
 
             let item_id = self.item_id();
             cx.observe_release(self, move |workspace, _, _| {
@@ -640,7 +640,7 @@ impl<T: Item> WeakItemHandle for WeakView<T> {
 }
 
 pub trait ProjectItem: Item {
-    type Item: project2::Item;
+    type Item: project::Item;
 
     fn for_project_item(
         project: Model<Project>,
@@ -662,19 +662,19 @@ pub trait FollowableEvents {
 pub trait FollowableItem: Item {
     type FollowableEvent: FollowableEvents;
     fn remote_id(&self) -> Option<ViewId>;
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
     fn from_state_proto(
         pane: View<Pane>,
         project: View<Workspace>,
         id: ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
+        cx: &mut WindowContext,
     ) -> Option<Task<Result<View<Self>>>>;
     fn add_event_to_update_proto(
         &self,
         event: &Self::FollowableEvent,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool;
     fn apply_update_proto(
         &mut self,
@@ -682,20 +682,20 @@ pub trait FollowableItem: Item {
         message: proto::update_view::Variant,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>>;
-    fn is_project_item(&self, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &WindowContext) -> bool;
 
     fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>);
 }
 
 pub trait FollowableItemHandle: ItemHandle {
-    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId>;
+    fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId>;
     fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant>;
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant>;
     fn add_event_to_update_proto(
         &self,
         event: &dyn Any,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool;
     fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
     fn apply_update_proto(
@@ -704,11 +704,11 @@ pub trait FollowableItemHandle: ItemHandle {
         message: proto::update_view::Variant,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn is_project_item(&self, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &WindowContext) -> bool;
 }
 
 impl<T: FollowableItem> FollowableItemHandle for View<T> {
-    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+    fn remote_id(&self, client: &Arc<Client>, cx: &WindowContext) -> Option<ViewId> {
         self.read(cx).remote_id().or_else(|| {
             client.peer_id().map(|creator| ViewId {
                 creator,
@@ -721,7 +721,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
     }
 
-    fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
         self.read(cx).to_state_proto(cx)
     }
 
@@ -729,7 +729,7 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         &self,
         event: &dyn Any,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool {
         if let Some(event) = event.downcast_ref() {
             self.read(cx).add_event_to_update_proto(event, update, cx)
@@ -754,305 +754,315 @@ impl<T: FollowableItem> FollowableItemHandle for View<T> {
         self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
     }
 
-    fn is_project_item(&self, cx: &AppContext) -> bool {
+    fn is_project_item(&self, cx: &WindowContext) -> bool {
         self.read(cx).is_project_item(cx)
     }
 }
 
-// #[cfg(any(test, feature = "test-support"))]
-// pub mod test {
-//     use super::{Item, ItemEvent};
-//     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
-//     use gpui::{
-//         elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
-//         ViewContext, View, WeakViewHandle,
-//     };
-//     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
-//     use smallvec::SmallVec;
-//     use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
-
-//     pub struct TestProjectItem {
-//         pub entry_id: Option<ProjectEntryId>,
-//         pub project_path: Option<ProjectPath>,
-//     }
-
-//     pub struct TestItem {
-//         pub workspace_id: WorkspaceId,
-//         pub state: String,
-//         pub label: String,
-//         pub save_count: usize,
-//         pub save_as_count: usize,
-//         pub reload_count: usize,
-//         pub is_dirty: bool,
-//         pub is_singleton: bool,
-//         pub has_conflict: bool,
-//         pub project_items: Vec<Model<TestProjectItem>>,
-//         pub nav_history: Option<ItemNavHistory>,
-//         pub tab_descriptions: Option<Vec<&'static str>>,
-//         pub tab_detail: Cell<Option<usize>>,
-//     }
-
-//     impl Entity for TestProjectItem {
-//         type Event = ();
-//     }
-
-//     impl project2::Item for TestProjectItem {
-//         fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
-//             self.entry_id
-//         }
-
-//         fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
-//             self.project_path.clone()
-//         }
-//     }
-
-//     pub enum TestItemEvent {
-//         Edit,
-//     }
-
-//     impl Clone for TestItem {
-//         fn clone(&self) -> Self {
-//             Self {
-//                 state: self.state.clone(),
-//                 label: self.label.clone(),
-//                 save_count: self.save_count,
-//                 save_as_count: self.save_as_count,
-//                 reload_count: self.reload_count,
-//                 is_dirty: self.is_dirty,
-//                 is_singleton: self.is_singleton,
-//                 has_conflict: self.has_conflict,
-//                 project_items: self.project_items.clone(),
-//                 nav_history: None,
-//                 tab_descriptions: None,
-//                 tab_detail: Default::default(),
-//                 workspace_id: self.workspace_id,
-//             }
-//         }
-//     }
-
-//     impl TestProjectItem {
-//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
-//             let entry_id = Some(ProjectEntryId::from_proto(id));
-//             let project_path = Some(ProjectPath {
-//                 worktree_id: WorktreeId::from_usize(0),
-//                 path: Path::new(path).into(),
-//             });
-//             cx.add_model(|_| Self {
-//                 entry_id,
-//                 project_path,
-//             })
-//         }
-
-//         pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
-//             cx.add_model(|_| Self {
-//                 project_path: None,
-//                 entry_id: None,
-//             })
-//         }
-//     }
-
-//     impl TestItem {
-//         pub fn new() -> Self {
-//             Self {
-//                 state: String::new(),
-//                 label: String::new(),
-//                 save_count: 0,
-//                 save_as_count: 0,
-//                 reload_count: 0,
-//                 is_dirty: false,
-//                 has_conflict: false,
-//                 project_items: Vec::new(),
-//                 is_singleton: true,
-//                 nav_history: None,
-//                 tab_descriptions: None,
-//                 tab_detail: Default::default(),
-//                 workspace_id: 0,
-//             }
-//         }
-
-//         pub fn new_deserialized(id: WorkspaceId) -> Self {
-//             let mut this = Self::new();
-//             this.workspace_id = id;
-//             this
-//         }
-
-//         pub fn with_label(mut self, state: &str) -> Self {
-//             self.label = state.to_string();
-//             self
-//         }
-
-//         pub fn with_singleton(mut self, singleton: bool) -> Self {
-//             self.is_singleton = singleton;
-//             self
-//         }
-
-//         pub fn with_dirty(mut self, dirty: bool) -> Self {
-//             self.is_dirty = dirty;
-//             self
-//         }
-
-//         pub fn with_conflict(mut self, has_conflict: bool) -> Self {
-//             self.has_conflict = has_conflict;
-//             self
-//         }
-
-//         pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
-//             self.project_items.clear();
-//             self.project_items.extend(items.iter().cloned());
-//             self
-//         }
-
-//         pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
-//             self.push_to_nav_history(cx);
-//             self.state = state;
-//         }
-
-//         fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
-//             if let Some(history) = &mut self.nav_history {
-//                 history.push(Some(Box::new(self.state.clone())), cx);
-//             }
-//         }
-//     }
-
-//     impl Entity for TestItem {
-//         type Event = TestItemEvent;
-//     }
-
-//     impl View for TestItem {
-//         fn ui_name() -> &'static str {
-//             "TestItem"
-//         }
-
-//         fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-//             Empty::new().into_any()
-//         }
-//     }
-
-//     impl Item for TestItem {
-//         fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
-//             self.tab_descriptions.as_ref().and_then(|descriptions| {
-//                 let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
-//                 Some(description.into())
-//             })
-//         }
-
-//         fn tab_content<V: 'static>(
-//             &self,
-//             detail: Option<usize>,
-//             _: &theme2::Tab,
-//             _: &AppContext,
-//         ) -> AnyElement<V> {
-//             self.tab_detail.set(detail);
-//             Empty::new().into_any()
-//         }
-
-//         fn for_each_project_item(
-//             &self,
-//             cx: &AppContext,
-//             f: &mut dyn FnMut(usize, &dyn project2::Item),
-//         ) {
-//             self.project_items
-//                 .iter()
-//                 .for_each(|item| f(item.id(), item.read(cx)))
-//         }
-
-// fn is_singleton(&self, _: &AppContext) -> bool {
-//     self.is_singleton
-// }
-
-//         fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
-//             self.nav_history = Some(history);
-//         }
-
-//         fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-//             let state = *state.downcast::<String>().unwrap_or_default();
-//             if state != self.state {
-//                 self.state = state;
-//                 true
-//             } else {
-//                 false
-//             }
-//         }
-
-//         fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-//             self.push_to_nav_history(cx);
-//         }
-
-//         fn clone_on_split(
-//             &self,
-//             _workspace_id: WorkspaceId,
-//             _: &mut ViewContext<Self>,
-//         ) -> Option<Self>
-//         where
-//             Self: Sized,
-//         {
-//             Some(self.clone())
-//         }
-
-//         fn is_dirty(&self, _: &AppContext) -> bool {
-//             self.is_dirty
-//         }
-
-//         fn has_conflict(&self, _: &AppContext) -> bool {
-//             self.has_conflict
-//         }
-
-//         fn can_save(&self, cx: &AppContext) -> bool {
-//             !self.project_items.is_empty()
-//                 && self
-//                     .project_items
-//                     .iter()
-//                     .all(|item| item.read(cx).entry_id.is_some())
-//         }
-
-//         fn save(
-//             &mut self,
-//             _: Model<Project>,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.save_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn save_as(
-//             &mut self,
-//             _: Model<Project>,
-//             _: std::path::PathBuf,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.save_as_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn reload(
-//             &mut self,
-//             _: Model<Project>,
-//             _: &mut ViewContext<Self>,
-//         ) -> Task<anyhow::Result<()>> {
-//             self.reload_count += 1;
-//             self.is_dirty = false;
-//             Task::ready(Ok(()))
-//         }
-
-//         fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-//             [ItemEvent::UpdateTab, ItemEvent::Edit].into()
-//         }
-
-//         fn serialized_item_kind() -> Option<&'static str> {
-//             Some("TestItem")
-//         }
-
-//         fn deserialize(
-//             _project: Model<Project>,
-//             _workspace: WeakViewHandle<Workspace>,
-//             workspace_id: WorkspaceId,
-//             _item_id: ItemId,
-//             cx: &mut ViewContext<Pane>,
-//         ) -> Task<anyhow::Result<View<Self>>> {
-//             let view = cx.add_view(|_cx| Self::new_deserialized(workspace_id));
-//             Task::Ready(Some(anyhow::Ok(view)))
-//         }
-//     }
-// }
+#[cfg(any(test, feature = "test-support"))]
+pub mod test {
+    use super::{Item, ItemEvent};
+    use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
+    use gpui::{
+        AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView,
+        IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView,
+    };
+    use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
+    use std::{any::Any, cell::Cell, path::Path};
+
+    pub struct TestProjectItem {
+        pub entry_id: Option<ProjectEntryId>,
+        pub project_path: Option<ProjectPath>,
+    }
+
+    pub struct TestItem {
+        pub workspace_id: WorkspaceId,
+        pub state: String,
+        pub label: String,
+        pub save_count: usize,
+        pub save_as_count: usize,
+        pub reload_count: usize,
+        pub is_dirty: bool,
+        pub is_singleton: bool,
+        pub has_conflict: bool,
+        pub project_items: Vec<Model<TestProjectItem>>,
+        pub nav_history: Option<ItemNavHistory>,
+        pub tab_descriptions: Option<Vec<&'static str>>,
+        pub tab_detail: Cell<Option<usize>>,
+        focus_handle: gpui::FocusHandle,
+    }
+
+    impl project::Item for TestProjectItem {
+        fn entry_id(&self, _: &AppContext) -> Option<ProjectEntryId> {
+            self.entry_id
+        }
+
+        fn project_path(&self, _: &AppContext) -> Option<ProjectPath> {
+            self.project_path.clone()
+        }
+    }
+
+    pub enum TestItemEvent {
+        Edit,
+    }
+
+    // impl Clone for TestItem {
+    //     fn clone(&self) -> Self {
+    //         Self {
+    //             state: self.state.clone(),
+    //             label: self.label.clone(),
+    //             save_count: self.save_count,
+    //             save_as_count: self.save_as_count,
+    //             reload_count: self.reload_count,
+    //             is_dirty: self.is_dirty,
+    //             is_singleton: self.is_singleton,
+    //             has_conflict: self.has_conflict,
+    //             project_items: self.project_items.clone(),
+    //             nav_history: None,
+    //             tab_descriptions: None,
+    //             tab_detail: Default::default(),
+    //             workspace_id: self.workspace_id,
+    //             focus_handle: self.focus_handle.clone(),
+    //         }
+    //     }
+    // }
+
+    impl TestProjectItem {
+        pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
+            let entry_id = Some(ProjectEntryId::from_proto(id));
+            let project_path = Some(ProjectPath {
+                worktree_id: WorktreeId::from_usize(0),
+                path: Path::new(path).into(),
+            });
+            cx.build_model(|_| Self {
+                entry_id,
+                project_path,
+            })
+        }
+
+        pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
+            cx.build_model(|_| Self {
+                project_path: None,
+                entry_id: None,
+            })
+        }
+    }
+
+    impl TestItem {
+        pub fn new(cx: &mut ViewContext<Self>) -> Self {
+            Self {
+                state: String::new(),
+                label: String::new(),
+                save_count: 0,
+                save_as_count: 0,
+                reload_count: 0,
+                is_dirty: false,
+                has_conflict: false,
+                project_items: Vec::new(),
+                is_singleton: true,
+                nav_history: None,
+                tab_descriptions: None,
+                tab_detail: Default::default(),
+                workspace_id: 0,
+                focus_handle: cx.focus_handle(),
+            }
+        }
+
+        pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
+            let mut this = Self::new(cx);
+            this.workspace_id = id;
+            this
+        }
+
+        pub fn with_label(mut self, state: &str) -> Self {
+            self.label = state.to_string();
+            self
+        }
+
+        pub fn with_singleton(mut self, singleton: bool) -> Self {
+            self.is_singleton = singleton;
+            self
+        }
+
+        pub fn with_dirty(mut self, dirty: bool) -> Self {
+            self.is_dirty = dirty;
+            self
+        }
+
+        pub fn with_conflict(mut self, has_conflict: bool) -> Self {
+            self.has_conflict = has_conflict;
+            self
+        }
+
+        pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
+            self.project_items.clear();
+            self.project_items.extend(items.iter().cloned());
+            self
+        }
+
+        pub fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
+            self.push_to_nav_history(cx);
+            self.state = state;
+        }
+
+        fn push_to_nav_history(&mut self, cx: &mut ViewContext<Self>) {
+            if let Some(history) = &mut self.nav_history {
+                history.push(Some(Box::new(self.state.clone())), cx);
+            }
+        }
+    }
+
+    impl Render for TestItem {
+        type Element = Div;
+
+        fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+            gpui::div()
+        }
+    }
+
+    impl EventEmitter<ItemEvent> for TestItem {}
+
+    impl FocusableView for TestItem {
+        fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+            self.focus_handle.clone()
+        }
+    }
+
+    impl Item for TestItem {
+        fn tab_description(&self, detail: usize, _: &AppContext) -> Option<SharedString> {
+            self.tab_descriptions.as_ref().and_then(|descriptions| {
+                let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
+                Some(description.into())
+            })
+        }
+
+        fn tab_content(
+            &self,
+            detail: Option<usize>,
+            cx: &ui::prelude::WindowContext,
+        ) -> AnyElement {
+            self.tab_detail.set(detail);
+            gpui::div().into_any_element()
+        }
+
+        fn for_each_project_item(
+            &self,
+            cx: &AppContext,
+            f: &mut dyn FnMut(EntityId, &dyn project::Item),
+        ) {
+            self.project_items
+                .iter()
+                .for_each(|item| f(item.entity_id(), item.read(cx)))
+        }
+
+        fn is_singleton(&self, _: &AppContext) -> bool {
+            self.is_singleton
+        }
+
+        fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+            self.nav_history = Some(history);
+        }
+
+        fn navigate(&mut self, state: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+            let state = *state.downcast::<String>().unwrap_or_default();
+            if state != self.state {
+                self.state = state;
+                true
+            } else {
+                false
+            }
+        }
+
+        fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+            self.push_to_nav_history(cx);
+        }
+
+        fn clone_on_split(
+            &self,
+            _workspace_id: WorkspaceId,
+            cx: &mut ViewContext<Self>,
+        ) -> Option<View<Self>>
+        where
+            Self: Sized,
+        {
+            Some(cx.build_view(|cx| Self {
+                state: self.state.clone(),
+                label: self.label.clone(),
+                save_count: self.save_count,
+                save_as_count: self.save_as_count,
+                reload_count: self.reload_count,
+                is_dirty: self.is_dirty,
+                is_singleton: self.is_singleton,
+                has_conflict: self.has_conflict,
+                project_items: self.project_items.clone(),
+                nav_history: None,
+                tab_descriptions: None,
+                tab_detail: Default::default(),
+                workspace_id: self.workspace_id,
+                focus_handle: cx.focus_handle(),
+            }))
+        }
+
+        fn is_dirty(&self, _: &AppContext) -> bool {
+            self.is_dirty
+        }
+
+        fn has_conflict(&self, _: &AppContext) -> bool {
+            self.has_conflict
+        }
+
+        fn can_save(&self, cx: &AppContext) -> bool {
+            !self.project_items.is_empty()
+                && self
+                    .project_items
+                    .iter()
+                    .all(|item| item.read(cx).entry_id.is_some())
+        }
+
+        fn save(
+            &mut self,
+            _: Model<Project>,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.save_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn save_as(
+            &mut self,
+            _: Model<Project>,
+            _: std::path::PathBuf,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.save_as_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn reload(
+            &mut self,
+            _: Model<Project>,
+            _: &mut ViewContext<Self>,
+        ) -> Task<anyhow::Result<()>> {
+            self.reload_count += 1;
+            self.is_dirty = false;
+            Task::ready(Ok(()))
+        }
+
+        fn serialized_item_kind() -> Option<&'static str> {
+            Some("TestItem")
+        }
+
+        fn deserialize(
+            _project: Model<Project>,
+            _workspace: WeakView<Workspace>,
+            workspace_id: WorkspaceId,
+            _item_id: ItemId,
+            cx: &mut ViewContext<Pane>,
+        ) -> Task<anyhow::Result<View<Self>>> {
+            let view = cx.build_view(|cx| Self::new_deserialized(workspace_id, cx));
+            Task::Ready(Some(anyhow::Ok(view)))
+        }
+    }
+}

crates/workspace2/src/pane.rs πŸ”—

@@ -13,9 +13,9 @@ use gpui::{
     VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
-use project2::{Project, ProjectEntryId, ProjectPath};
+use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
 use std::{
     any::Any,
     cmp, fmt, mem,
@@ -507,6 +507,28 @@ impl Pane {
         !self.nav_history.0.lock().forward_stack.is_empty()
     }
 
+    fn navigate_backward(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(workspace) = self.workspace.upgrade() {
+            let pane = cx.view().downgrade();
+            cx.window_context().defer(move |cx| {
+                workspace.update(cx, |workspace, cx| {
+                    workspace.go_back(pane, cx).detach_and_log_err(cx)
+                })
+            })
+        }
+    }
+
+    fn navigate_forward(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(workspace) = self.workspace.upgrade() {
+            let pane = cx.view().downgrade();
+            cx.window_context().defer(move |cx| {
+                workspace.update(cx, |workspace, cx| {
+                    workspace.go_forward(pane, cx).detach_and_log_err(cx)
+                })
+            })
+        }
+    }
+
     fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
         self.toolbar.update(cx, |_, cx| cx.notify());
     }
@@ -1556,12 +1578,20 @@ impl Pane {
                             .child(
                                 div().border().border_color(gpui::red()).child(
                                     IconButton::new("navigate_backward", Icon::ArrowLeft)
+                                        .on_click({
+                                            let view = cx.view().clone();
+                                            move |_, cx| view.update(cx, Self::navigate_backward)
+                                        })
                                         .disabled(!self.can_navigate_backward()),
                                 ),
                             )
                             .child(
                                 div().border().border_color(gpui::red()).child(
                                     IconButton::new("navigate_forward", Icon::ArrowRight)
+                                        .on_click({
+                                            let view = cx.view().clone();
+                                            move |_, cx| view.update(cx, Self::navigate_backward)
+                                        })
                                         .disabled(!self.can_navigate_forward()),
                                 ),
                             ),
@@ -2089,18 +2119,14 @@ impl Render for Pane {
                     this.update(cx, |this, cx| this.focus_out(cx)).ok();
                 }
             })
-            .on_action(cx.listener(|pane: &mut Pane, _: &SplitLeft, cx| {
-                pane.split(SplitDirection::Left, cx)
-            }))
+            .on_action(cx.listener(|pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx)))
+            .on_action(cx.listener(|pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)))
             .on_action(
-                cx.listener(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx)),
+                cx.listener(|pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx)),
             )
-            .on_action(cx.listener(|pane: &mut Pane, _: &SplitRight, cx| {
-                pane.split(SplitDirection::Right, cx)
-            }))
-            .on_action(cx.listener(|pane: &mut Pane, _: &SplitDown, cx| {
-                pane.split(SplitDirection::Down, cx)
-            }))
+            .on_action(cx.listener(|pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)))
+            .on_action(cx.listener(|pane, _: &GoBack, cx| pane.navigate_backward(cx)))
+            .on_action(cx.listener(|pane, _: &GoForward, cx| pane.navigate_forward(cx)))
             .on_action(cx.listener(Pane::toggle_zoom))
             .on_action(cx.listener(|pane: &mut Pane, action: &ActivateItem, cx| {
                 pane.activate_item(action.0, true, true, cx);

crates/workspace2/src/pane_group.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::{AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, bail, Result};
 use collections::HashMap;
-use db2::sqlez::{
+use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
@@ -9,7 +9,7 @@ use gpui::{
     point, size, AnyWeakView, Bounds, Div, IntoElement, Model, Pixels, Point, View, ViewContext,
 };
 use parking_lot::Mutex;
-use project2::Project;
+use project::Project;
 use serde::Deserialize;
 use std::sync::Arc;
 use ui::prelude::*;

crates/workspace2/src/persistence.rs πŸ”—

@@ -5,7 +5,7 @@ pub mod model;
 use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
-use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
 use gpui::WindowBounds;
 
 use util::{unzip_option, ResultExt};
@@ -552,7 +552,7 @@ impl WorkspaceDb {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use db2::open_test_db;
+    use db::open_test_db;
     use gpui;
 
     #[gpui::test]

crates/workspace2/src/persistence/model.rs πŸ”—

@@ -3,12 +3,12 @@ use crate::{
 };
 use anyhow::{Context, Result};
 use async_recursion::async_recursion;
-use db2::sqlez::{
+use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
 use gpui::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
-use project2::Project;
+use project::Project;
 use std::{
     path::{Path, PathBuf},
     sync::Arc,

crates/workspace2/src/searchable.rs πŸ”—

@@ -4,7 +4,7 @@ use gpui::{
     AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
     WindowContext,
 };
-use project2::search::SearchQuery;
+use project::search::SearchQuery;
 
 use crate::{
     item::{Item, WeakItemHandle},

crates/workspace2/src/status_bar.rs πŸ”—

@@ -52,22 +52,13 @@ impl Render for StatusBar {
                 h_stack()
                     .gap_4()
                     .child(
-                        h_stack()
-                            .gap_1()
-                            .child(
-                                // TODO: Line / column numbers
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(Button::new("status_line_column_numbers", "15:22")),
-                            )
-                            .child(
-                                // TODO: Language picker
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(Button::new("status_buffer_language", "Rust")),
-                            ),
+                        h_stack().gap_1().child(
+                            // TODO: Language picker
+                            div()
+                                .border()
+                                .border_color(gpui::red())
+                                .child(Button::new("status_buffer_language", "Rust")),
+                        ),
                     )
                     .child(
                         h_stack()
@@ -133,7 +124,7 @@ impl StatusBar {
         h_stack()
             .items_center()
             .gap_2()
-            .children(self.right_items.iter().map(|item| item.to_any()))
+            .children(self.right_items.iter().rev().map(|item| item.to_any()))
     }
 }
 

crates/workspace2/src/workspace2.rs πŸ”—

@@ -16,7 +16,7 @@ mod workspace_settings;
 
 use anyhow::{anyhow, Context as _, Result};
 use async_trait::async_trait;
-use client2::{
+use client::{
     proto::{self, PeerId},
     Client, TypedEnvelope, User, UserStore,
 };
@@ -37,7 +37,7 @@ use gpui::{
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
-use language2::{LanguageRegistry, Rope};
+use language::{LanguageRegistry, Rope};
 use lazy_static::lazy_static;
 pub use modal_layer::*;
 use node_runtime::NodeRuntime;
@@ -49,9 +49,9 @@ pub use persistence::{
     WorkspaceDb, DB,
 };
 use postage::stream::Stream;
-use project2::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
+use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
 use serde::Deserialize;
-use settings2::Settings;
+use settings::Settings;
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
 use std::{
@@ -62,7 +62,7 @@ use std::{
     sync::{atomic::AtomicUsize, Arc},
     time::Duration,
 };
-use theme2::{ActiveTheme, ThemeSettings};
+use theme::{ActiveTheme, ThemeSettings};
 pub use toolbar::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 pub use ui;
 use util::ResultExt;
@@ -247,7 +247,7 @@ type FollowableItemBuilder = fn(
     View<Workspace>,
     ViewId,
     &mut Option<proto::view::Variant>,
-    &mut AppContext,
+    &mut WindowContext,
 ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 type FollowableItemBuilders = HashMap<
     TypeId,
@@ -301,7 +301,7 @@ pub struct AppState {
     pub client: Arc<Client>,
     pub user_store: Model<UserStore>,
     pub workspace_store: Model<WorkspaceStore>,
-    pub fs: Arc<dyn fs2::Fs>,
+    pub fs: Arc<dyn fs::Fs>,
     pub call_factory: CallFactory,
     pub build_window_options:
         fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
@@ -312,7 +312,7 @@ pub struct WorkspaceStore {
     workspaces: HashSet<WindowHandle<Workspace>>,
     followers: Vec<Follower>,
     client: Arc<Client>,
-    _subscriptions: Vec<client2::Subscription>,
+    _subscriptions: Vec<client::Subscription>,
 }
 
 #[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
@@ -388,22 +388,22 @@ impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
         use node_runtime::FakeNodeRuntime;
-        use settings2::SettingsStore;
+        use settings::SettingsStore;
 
         if !cx.has_global::<SettingsStore>() {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
         }
 
-        let fs = fs2::FakeFs::new(cx.background_executor().clone());
+        let fs = fs::FakeFs::new(cx.background_executor().clone());
         let languages = Arc::new(LanguageRegistry::test());
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone(), cx);
         let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
         let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
-        theme2::init(theme2::LoadThemes::JustBase, cx);
-        client2::init(&client, cx);
+        theme::init(theme::LoadThemes::JustBase, cx);
+        client::init(&client, cx);
         crate::init_settings(cx);
 
         Arc::new(Self {
@@ -567,29 +567,29 @@ impl Workspace {
         cx.observe(&project, |_, _, cx| cx.notify()).detach();
         cx.subscribe(&project, move |this, _, event, cx| {
             match event {
-                project2::Event::RemoteIdChanged(_) => {
+                project::Event::RemoteIdChanged(_) => {
                     this.update_window_title(cx);
                 }
 
-                project2::Event::CollaboratorLeft(peer_id) => {
+                project::Event::CollaboratorLeft(peer_id) => {
                     this.collaborator_left(*peer_id, cx);
                 }
 
-                project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
                     this.update_window_title(cx);
                     this.serialize_workspace(cx);
                 }
 
-                project2::Event::DisconnectedFromHost => {
+                project::Event::DisconnectedFromHost => {
                     this.update_window_edited(cx);
                     cx.blur();
                 }
 
-                project2::Event::Closed => {
+                project::Event::Closed => {
                     cx.remove_window();
                 }
 
-                project2::Event::DeletedEntry(entry_id) => {
+                project::Event::DeletedEntry(entry_id) => {
                     for pane in this.panes.iter() {
                         pane.update(cx, |pane, cx| {
                             pane.handle_deleted_project_item(*entry_id, cx)
@@ -597,7 +597,7 @@ impl Workspace {
                     }
                 }
 
-                project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
                     cx.build_view(|_| MessageNotification::new(message.clone()))
                 }),
 
@@ -1450,7 +1450,7 @@ impl Workspace {
                             .map(|entry| entry.id);
                             if let Some(entry_id) = entry_id {
                                 workspace.project.update(cx, |_, cx| {
-                                    cx.emit(project2::Event::ActiveEntryChanged(Some(entry_id)));
+                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
                                 })
                             }
                         })
@@ -1812,8 +1812,7 @@ impl Workspace {
         });
         cx.subscribe(&pane, Self::handle_pane_event).detach();
         self.panes.push(pane.clone());
-        // todo!()
-        // cx.focus(&pane);
+        cx.focus_view(&pane);
         cx.emit(Event::PaneAdded(pane.clone()));
         pane
     }
@@ -1988,7 +1987,7 @@ impl Workspace {
     where
         T: ProjectItem,
     {
-        use project2::Item as _;
+        use project::Item as _;
 
         let entry_id = project_item.read(cx).entry_id(cx);
         if let Some(item) = entry_id
@@ -2013,7 +2012,7 @@ impl Workspace {
     where
         T: ProjectItem,
     {
-        use project2::Item as _;
+        use project::Item as _;
 
         let entry_id = project_item.read(cx).entry_id(cx);
         if let Some(item) = entry_id
@@ -2592,8 +2591,7 @@ impl Workspace {
             title.push_str(" β†—");
         }
 
-        // todo!()
-        // cx.set_window_title(&title);
+        cx.set_window_title(&title);
     }
 
     fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
@@ -3592,7 +3590,7 @@ fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncA
 
     workspace
         .update(cx, |workspace, cx| {
-            if (*db2::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
+            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
                 workspace.show_notification_once(0, cx, |cx| {
                     cx.build_view(|_| {
                         MessageNotification::new("Failed to load the database file.")
@@ -4473,960 +4471,950 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
     Some(size((width as f64).into(), (height as f64).into()))
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-//     use crate::{
-//         dock::test::TestPanel,
-//         item::test::{TestItem, TestItemEvent, TestProjectItem},
-//     };
-//     use fs::FakeFs;
-//     use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
-//     use project::{Project, ProjectEntryId};
-//     use serde_json::json;
-//     use settings::SettingsStore;
-//     use std::{cell::RefCell, rc::Rc};
-
-//     #[gpui::test]
-//     async fn test_tab_disambiguation(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-
-//         // Adding an item with no ambiguity renders the tab without detail.
-//         let item1 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item1.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
-
-//         // Adding an item that creates ambiguity increases the level of detail on
-//         // both tabs.
-//         let item2 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item2.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-
-//         // Adding an item that creates ambiguity increases the level of detail only
-//         // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
-//         // we stop at the highest detail available.
-//         let item3 = window.build_view(cx, |_| {
-//             let mut item = TestItem::new();
-//             item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
-//             item
-//         });
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item3.clone()), cx);
-//         });
-//         item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
-//         item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-//         item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
-//     }
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::item::{
+        test::{TestItem, TestProjectItem},
+        ItemEvent,
+    };
+    use fs::FakeFs;
+    use gpui::TestAppContext;
+    use project::{Project, ProjectEntryId};
+    use serde_json::json;
+    use settings::SettingsStore;
+    use std::{cell::RefCell, rc::Rc};
+
+    #[gpui::test]
+    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+
+        // Adding an item with no ambiguity renders the tab without detail.
+        let item1 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item1.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(0)));
+
+        // Adding an item that creates ambiguity increases the level of detail on
+        // both tabs.
+        let item2 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item2.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+
+        // Adding an item that creates ambiguity increases the level of detail only
+        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
+        // we stop at the highest detail available.
+        let item3 = cx.build_view(|cx| {
+            let mut item = TestItem::new(cx);
+            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
+            item
+        });
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item3.clone()), cx);
+        });
+        item1.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
+        item2.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+        item3.update(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
+    }
 
-//     #[gpui::test]
-//     async fn test_tracking_active_path(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         fs.insert_tree(
-//             "/root1",
-//             json!({
-//                 "one.txt": "",
-//                 "two.txt": "",
-//             }),
-//         )
-//         .await;
-//         fs.insert_tree(
-//             "/root2",
-//             json!({
-//                 "three.txt": "",
-//             }),
-//         )
-//         .await;
+    #[gpui::test]
+    async fn test_tracking_active_path(cx: &mut TestAppContext) {
+        init_test(cx);
 
-//         let project = Project::test(fs, ["root1".as_ref()], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         let worktree_id = project.read_with(cx, |project, cx| {
-//             project.worktrees().next().unwrap().read(cx).id()
-//         });
+        let fs = FakeFs::new(cx.executor());
+        fs.insert_tree(
+            "/root1",
+            json!({
+                "one.txt": "",
+                "two.txt": "",
+            }),
+        )
+        .await;
+        fs.insert_tree(
+            "/root2",
+            json!({
+                "three.txt": "",
+            }),
+        )
+        .await;
 
-//         let item1 = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-//         });
-//         let item2 = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
-//         });
+        let project = Project::test(fs, ["root1".as_ref()], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+        let worktree_id = project.read_with(cx, |project, cx| {
+            project.worktrees().next().unwrap().read(cx).id()
+        });
 
-//         // Add an item to an empty pane
-//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
-
-//         // Add a second item to a non-empty pane
-//         workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
-//         assert_eq!(window.current_title(cx).as_deref(), Some("two.txt β€” root1"));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "two.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
+        let item1 = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
+        });
+        let item2 = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
+        });
 
-//         // Close the active item
-//         pane.update(cx, |pane, cx| {
-//             pane.close_active_item(&Default::default(), cx).unwrap()
-//         })
-//         .await
-//         .unwrap();
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root1"));
-//         project.read_with(cx, |project, cx| {
-//             assert_eq!(
-//                 project.active_entry(),
-//                 project
-//                     .entry_for_path(&(worktree_id, "one.txt").into(), cx)
-//                     .map(|e| e.id)
-//             );
-//         });
+        // Add an item to an empty pane
+        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
+        project.read_with(cx, |project, cx| {
+            assert_eq!(
+                project.active_entry(),
+                project
+                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+                    .map(|e| e.id)
+            );
+        });
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
+
+        // Add a second item to a non-empty pane
+        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
+        assert_eq!(cx.window_title().as_deref(), Some("two.txt β€” root1"));
+        project.read_with(cx, |project, cx| {
+            assert_eq!(
+                project.active_entry(),
+                project
+                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
+                    .map(|e| e.id)
+            );
+        });
 
-//         // Add a project folder
-//         project
-//             .update(cx, |project, cx| {
-//                 project.find_or_create_local_worktree("/root2", true, cx)
-//             })
-//             .await
-//             .unwrap();
-//         assert_eq!(
-//             window.current_title(cx).as_deref(),
-//             Some("one.txt β€” root1, root2")
-//         );
-
-//         // Remove a project folder
-//         project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
-//         assert_eq!(window.current_title(cx).as_deref(), Some("one.txt β€” root2"));
-//     }
+        // Close the active item
+        pane.update(cx, |pane, cx| {
+            pane.close_active_item(&Default::default(), cx).unwrap()
+        })
+        .await
+        .unwrap();
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1"));
+        project.read_with(cx, |project, cx| {
+            assert_eq!(
+                project.active_entry(),
+                project
+                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
+                    .map(|e| e.id)
+            );
+        });
 
-//     #[gpui::test]
-//     async fn test_close_window(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-//         fs.insert_tree("/root", json!({ "one": "" })).await;
-
-//         let project = Project::test(fs, ["root".as_ref()], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-
-//         // When there are no dirty items, there's nothing to do.
-//         let item1 = window.build_view(cx, |_| TestItem::new());
-//         workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
-//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-//         assert!(task.await.unwrap());
-
-//         // When there are dirty untitled items, prompt to save each one. If the user
-//         // cancels any prompt, then abort.
-//         let item2 = window.build_view(cx, |_| TestItem::new().with_dirty(true));
-//         let item3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         workspace.update(cx, |w, cx| {
-//             w.add_item(Box::new(item2.clone()), cx);
-//             w.add_item(Box::new(item3.clone()), cx);
-//         });
-//         let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
-//         cx.foreground().run_until_parked();
-//         window.simulate_prompt_answer(2, cx); // cancel save all
-//         cx.foreground().run_until_parked();
-//         window.simulate_prompt_answer(2, cx); // cancel save all
-//         cx.foreground().run_until_parked();
-//         assert!(!window.has_pending_prompt(cx));
-//         assert!(!task.await.unwrap());
-//     }
+        // Add a project folder
+        project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/root2", true, cx)
+            })
+            .await
+            .unwrap();
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
 
-//     #[gpui::test]
-//     async fn test_close_pane_items(cx: &mut TestAppContext) {
-//         init_test(cx);
+        // Remove a project folder
+        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
+        assert_eq!(cx.window_title().as_deref(), Some("one.txt β€” root2"));
+    }
 
-//         let fs = FakeFs::new(cx.background());
+    #[gpui::test]
+    async fn test_close_window(cx: &mut TestAppContext) {
+        init_test(cx);
 
-//         let project = Project::test(fs, None, cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        let fs = FakeFs::new(cx.executor());
+        fs.insert_tree("/root", json!({ "one": "" })).await;
 
-//         let item1 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let item2 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_conflict(true)
-//                 .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-//         });
-//         let item3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_conflict(true)
-//                 .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
-//         });
-//         let item4 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_project_items(&[TestProjectItem::new_untitled(cx)])
-//         });
-//         let pane = workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item1.clone()), cx);
-//             workspace.add_item(Box::new(item2.clone()), cx);
-//             workspace.add_item(Box::new(item3.clone()), cx);
-//             workspace.add_item(Box::new(item4.clone()), cx);
-//             workspace.active_pane().clone()
-//         });
+        let project = Project::test(fs, ["root".as_ref()], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
 
-//         let close_items = pane.update(cx, |pane, cx| {
-//             pane.activate_item(1, true, true, cx);
-//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
-//             let item1_id = item1.id();
-//             let item3_id = item3.id();
-//             let item4_id = item4.id();
-//             pane.close_items(cx, SaveIntent::Close, move |id| {
-//                 [item1_id, item3_id, item4_id].contains(&id)
-//             })
-//         });
-//         cx.foreground().run_until_parked();
-
-//         assert!(window.has_pending_prompt(cx));
-//         // Ignore "Save all" prompt
-//         window.simulate_prompt_answer(2, cx);
-//         cx.foreground().run_until_parked();
-//         // There's a prompt to save item 1.
-//         pane.read_with(cx, |pane, _| {
-//             assert_eq!(pane.items_len(), 4);
-//             assert_eq!(pane.active_item().unwrap().id(), item1.id());
-//         });
-//         // Confirm saving item 1.
-//         window.simulate_prompt_answer(0, cx);
-//         cx.foreground().run_until_parked();
-
-//         // Item 1 is saved. There's a prompt to save item 3.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item1.read(cx).save_count, 1);
-//             assert_eq!(item1.read(cx).save_as_count, 0);
-//             assert_eq!(item1.read(cx).reload_count, 0);
-//             assert_eq!(pane.items_len(), 3);
-//             assert_eq!(pane.active_item().unwrap().id(), item3.id());
-//         });
-//         assert!(window.has_pending_prompt(cx));
-
-//         // Cancel saving item 3.
-//         window.simulate_prompt_answer(1, cx);
-//         cx.foreground().run_until_parked();
-
-//         // Item 3 is reloaded. There's a prompt to save item 4.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item3.read(cx).save_count, 0);
-//             assert_eq!(item3.read(cx).save_as_count, 0);
-//             assert_eq!(item3.read(cx).reload_count, 1);
-//             assert_eq!(pane.items_len(), 2);
-//             assert_eq!(pane.active_item().unwrap().id(), item4.id());
-//         });
-//         assert!(window.has_pending_prompt(cx));
-
-//         // Confirm saving item 4.
-//         window.simulate_prompt_answer(0, cx);
-//         cx.foreground().run_until_parked();
-
-//         // There's a prompt for a path for item 4.
-//         cx.simulate_new_path_selection(|_| Some(Default::default()));
-//         close_items.await.unwrap();
-
-//         // The requested items are closed.
-//         pane.read_with(cx, |pane, cx| {
-//             assert_eq!(item4.read(cx).save_count, 0);
-//             assert_eq!(item4.read(cx).save_as_count, 1);
-//             assert_eq!(item4.read(cx).reload_count, 0);
-//             assert_eq!(pane.items_len(), 1);
-//             assert_eq!(pane.active_item().unwrap().id(), item2.id());
-//         });
-//     }
+        // When there are no dirty items, there's nothing to do.
+        let item1 = cx.build_view(|cx| TestItem::new(cx));
+        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
+        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+        assert!(task.await.unwrap());
 
-//     #[gpui::test]
-//     async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
-//         init_test(cx);
-
-//         let fs = FakeFs::new(cx.background());
-
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-
-//         // Create several workspace items with single project entries, and two
-//         // workspace items with multiple project entries.
-//         let single_entry_items = (0..=4)
-//             .map(|project_entry_id| {
-//                 window.build_view(cx, |cx| {
-//                     TestItem::new()
-//                         .with_dirty(true)
-//                         .with_project_items(&[TestProjectItem::new(
-//                             project_entry_id,
-//                             &format!("{project_entry_id}.txt"),
-//                             cx,
-//                         )])
-//                 })
-//             })
-//             .collect::<Vec<_>>();
-//         let item_2_3 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_singleton(false)
-//                 .with_project_items(&[
-//                     single_entry_items[2].read(cx).project_items[0].clone(),
-//                     single_entry_items[3].read(cx).project_items[0].clone(),
-//                 ])
-//         });
-//         let item_3_4 = window.build_view(cx, |cx| {
-//             TestItem::new()
-//                 .with_dirty(true)
-//                 .with_singleton(false)
-//                 .with_project_items(&[
-//                     single_entry_items[3].read(cx).project_items[0].clone(),
-//                     single_entry_items[4].read(cx).project_items[0].clone(),
-//                 ])
-//         });
+        // When there are dirty untitled items, prompt to save each one. If the user
+        // cancels any prompt, then abort.
+        let item2 = cx.build_view(|cx| TestItem::new(cx).with_dirty(true));
+        let item3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        workspace.update(cx, |w, cx| {
+            w.add_item(Box::new(item2.clone()), cx);
+            w.add_item(Box::new(item3.clone()), cx);
+        });
+        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
+        cx.executor().run_until_parked();
+        cx.simulate_prompt_answer(2); // cancel save all
+        cx.executor().run_until_parked();
+        cx.simulate_prompt_answer(2); // cancel save all
+        cx.executor().run_until_parked();
+        assert!(!cx.has_pending_prompt());
+        assert!(!task.await.unwrap());
+    }
 
-//         // Create two panes that contain the following project entries:
-//         //   left pane:
-//         //     multi-entry items:   (2, 3)
-//         //     single-entry items:  0, 1, 2, 3, 4
-//         //   right pane:
-//         //     single-entry items:  1
-//         //     multi-entry items:   (3, 4)
-//         let left_pane = workspace.update(cx, |workspace, cx| {
-//             let left_pane = workspace.active_pane().clone();
-//             workspace.add_item(Box::new(item_2_3.clone()), cx);
-//             for item in single_entry_items {
-//                 workspace.add_item(Box::new(item), cx);
-//             }
-//             left_pane.update(cx, |pane, cx| {
-//                 pane.activate_item(2, true, true, cx);
-//             });
+    #[gpui::test]
+    async fn test_close_pane_items(cx: &mut TestAppContext) {
+        init_test(cx);
 
-//             workspace
-//                 .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
-//                 .unwrap();
+        let fs = FakeFs::new(cx.executor());
 
-//             left_pane
-//         });
+        let project = Project::test(fs, None, cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
 
-//         //Need to cause an effect flush in order to respect new focus
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item_3_4.clone()), cx);
-//             cx.focus(&left_pane);
-//         });
+        let item1 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let item2 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_conflict(true)
+                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
+        });
+        let item3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_conflict(true)
+                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
+        });
+        let item4 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_project_items(&[TestProjectItem::new_untitled(cx)])
+        });
+        let pane = workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item1.clone()), cx);
+            workspace.add_item(Box::new(item2.clone()), cx);
+            workspace.add_item(Box::new(item3.clone()), cx);
+            workspace.add_item(Box::new(item4.clone()), cx);
+            workspace.active_pane().clone()
+        });
 
-//         // When closing all of the items in the left pane, we should be prompted twice:
-//         // once for project entry 0, and once for project entry 2. After those two
-//         // prompts, the task should complete.
+        let close_items = pane.update(cx, |pane, cx| {
+            pane.activate_item(1, true, true, cx);
+            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+            let item1_id = item1.item_id();
+            let item3_id = item3.item_id();
+            let item4_id = item4.item_id();
+            pane.close_items(cx, SaveIntent::Close, move |id| {
+                [item1_id, item3_id, item4_id].contains(&id)
+            })
+        });
+        cx.executor().run_until_parked();
+
+        assert!(cx.has_pending_prompt());
+        // Ignore "Save all" prompt
+        cx.simulate_prompt_answer(2);
+        cx.executor().run_until_parked();
+        // There's a prompt to save item 1.
+        pane.update(cx, |pane, _| {
+            assert_eq!(pane.items_len(), 4);
+            assert_eq!(pane.active_item().unwrap().item_id(), item1.item_id());
+        });
+        // Confirm saving item 1.
+        cx.simulate_prompt_answer(0);
+        cx.executor().run_until_parked();
+
+        // Item 1 is saved. There's a prompt to save item 3.
+        pane.update(cx, |pane, cx| {
+            assert_eq!(item1.read(cx).save_count, 1);
+            assert_eq!(item1.read(cx).save_as_count, 0);
+            assert_eq!(item1.read(cx).reload_count, 0);
+            assert_eq!(pane.items_len(), 3);
+            assert_eq!(pane.active_item().unwrap().item_id(), item3.item_id());
+        });
+        assert!(cx.has_pending_prompt());
+
+        // Cancel saving item 3.
+        cx.simulate_prompt_answer(1);
+        cx.executor().run_until_parked();
+
+        // Item 3 is reloaded. There's a prompt to save item 4.
+        pane.update(cx, |pane, cx| {
+            assert_eq!(item3.read(cx).save_count, 0);
+            assert_eq!(item3.read(cx).save_as_count, 0);
+            assert_eq!(item3.read(cx).reload_count, 1);
+            assert_eq!(pane.items_len(), 2);
+            assert_eq!(pane.active_item().unwrap().item_id(), item4.item_id());
+        });
+        assert!(cx.has_pending_prompt());
+
+        // Confirm saving item 4.
+        cx.simulate_prompt_answer(0);
+        cx.executor().run_until_parked();
+
+        // There's a prompt for a path for item 4.
+        cx.simulate_new_path_selection(|_| Some(Default::default()));
+        close_items.await.unwrap();
+
+        // The requested items are closed.
+        pane.update(cx, |pane, cx| {
+            assert_eq!(item4.read(cx).save_count, 0);
+            assert_eq!(item4.read(cx).save_as_count, 1);
+            assert_eq!(item4.read(cx).reload_count, 0);
+            assert_eq!(pane.items_len(), 1);
+            assert_eq!(pane.active_item().unwrap().item_id(), item2.item_id());
+        });
+    }
 
-//         let close = left_pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |_| true)
-//         });
-//         cx.foreground().run_until_parked();
-//         // Discard "Save all" prompt
-//         window.simulate_prompt_answer(2, cx);
-
-//         cx.foreground().run_until_parked();
-//         left_pane.read_with(cx, |pane, cx| {
-//             assert_eq!(
-//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-//                 &[ProjectEntryId::from_proto(0)]
-//             );
-//         });
-//         window.simulate_prompt_answer(0, cx);
+    #[gpui::test]
+    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
+        init_test(cx);
+
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+
+        // Create several workspace items with single project entries, and two
+        // workspace items with multiple project entries.
+        let single_entry_items = (0..=4)
+            .map(|project_entry_id| {
+                cx.build_view(|cx| {
+                    TestItem::new(cx)
+                        .with_dirty(true)
+                        .with_project_items(&[TestProjectItem::new(
+                            project_entry_id,
+                            &format!("{project_entry_id}.txt"),
+                            cx,
+                        )])
+                })
+            })
+            .collect::<Vec<_>>();
+        let item_2_3 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_singleton(false)
+                .with_project_items(&[
+                    single_entry_items[2].read(cx).project_items[0].clone(),
+                    single_entry_items[3].read(cx).project_items[0].clone(),
+                ])
+        });
+        let item_3_4 = cx.build_view(|cx| {
+            TestItem::new(cx)
+                .with_dirty(true)
+                .with_singleton(false)
+                .with_project_items(&[
+                    single_entry_items[3].read(cx).project_items[0].clone(),
+                    single_entry_items[4].read(cx).project_items[0].clone(),
+                ])
+        });
 
-//         cx.foreground().run_until_parked();
-//         left_pane.read_with(cx, |pane, cx| {
-//             assert_eq!(
-//                 pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
-//                 &[ProjectEntryId::from_proto(2)]
-//             );
-//         });
-//         window.simulate_prompt_answer(0, cx);
+        // Create two panes that contain the following project entries:
+        //   left pane:
+        //     multi-entry items:   (2, 3)
+        //     single-entry items:  0, 1, 2, 3, 4
+        //   right pane:
+        //     single-entry items:  1
+        //     multi-entry items:   (3, 4)
+        let left_pane = workspace.update(cx, |workspace, cx| {
+            let left_pane = workspace.active_pane().clone();
+            workspace.add_item(Box::new(item_2_3.clone()), cx);
+            for item in single_entry_items {
+                workspace.add_item(Box::new(item), cx);
+            }
+            left_pane.update(cx, |pane, cx| {
+                pane.activate_item(2, true, true, cx);
+            });
 
-//         cx.foreground().run_until_parked();
-//         close.await.unwrap();
-//         left_pane.read_with(cx, |pane, _| {
-//             assert_eq!(pane.items_len(), 0);
-//         });
-//     }
+            let right_pane = workspace
+                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
+                .unwrap();
 
-//     #[gpui::test]
-//     async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
+            right_pane.update(cx, |pane, cx| {
+                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
+            });
 
-//         let fs = FakeFs::new(cx.background());
+            left_pane
+        });
 
-//         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());
+        cx.focus_view(&left_pane);
 
-//         let item = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let item_id = item.id();
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//         });
+        // When closing all of the items in the left pane, we should be prompted twice:
+        // once for project entry 0, and once for project entry 2. Project entries 1,
+        // 3, and 4 are all still open in the other paten. After those two
+        // prompts, the task should complete.
 
-//         // Autosave on window change.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnWindowChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        let close = left_pane.update(cx, |pane, cx| {
+            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
+        });
+        cx.executor().run_until_parked();
 
-//         // Deactivating the window saves the file.
-//         window.simulate_deactivation(cx);
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
-
-//         // Autosave on focus change.
-//         item.update(cx, |item, cx| {
-//             cx.focus_self();
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        // Discard "Save all" prompt
+        cx.simulate_prompt_answer(2);
 
-//         // Blurring the item saves the file.
-//         item.update(cx, |_, cx| cx.blur());
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
+        cx.executor().run_until_parked();
+        left_pane.update(cx, |pane, cx| {
+            assert_eq!(
+                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+                &[ProjectEntryId::from_proto(0)]
+            );
+        });
+        cx.simulate_prompt_answer(0);
 
-//         // Deactivating the window still saves the file.
-//         window.simulate_activation(cx);
-//         item.update(cx, |item, cx| {
-//             cx.focus_self();
-//             item.is_dirty = true;
-//         });
-//         window.simulate_deactivation(cx);
+        cx.executor().run_until_parked();
+        left_pane.update(cx, |pane, cx| {
+            assert_eq!(
+                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
+                &[ProjectEntryId::from_proto(2)]
+            );
+        });
+        cx.simulate_prompt_answer(0);
 
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+        cx.executor().run_until_parked();
+        close.await.unwrap();
+        left_pane.update(cx, |pane, _| {
+            assert_eq!(pane.items_len(), 0);
+        });
+    }
 
-//         // Autosave after delay.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
-//                 })
-//             });
-//             item.is_dirty = true;
-//             cx.emit(TestItemEvent::Edit);
-//         });
+    #[gpui::test]
+    async fn test_autosave(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
 
-//         // Delay hasn't fully expired, so the file is still dirty and unsaved.
-//         deterministic.advance_clock(Duration::from_millis(250));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
+        let fs = FakeFs::new(cx.executor());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
 
-//         // After delay expires, the file is saved.
-//         deterministic.advance_clock(Duration::from_millis(250));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
+        let item = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let item_id = item.entity_id();
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+        });
 
-//         // Autosave on focus change, ensuring closing the tab counts as such.
-//         item.update(cx, |item, cx| {
-//             cx.update_global(|settings: &mut SettingsStore, cx| {
-//                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-//                     settings.autosave = Some(AutosaveSetting::OnFocusChange);
-//                 })
-//             });
-//             item.is_dirty = true;
-//         });
+        // Autosave on window change.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-//         })
-//         .await
-//         .unwrap();
-//         assert!(!window.has_pending_prompt(cx));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-
-//         // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//         });
-//         item.update(cx, |item, cx| {
-//             item.project_items[0].update(cx, |item, _| {
-//                 item.entry_id = None;
-//             });
-//             item.is_dirty = true;
-//             cx.blur();
-//         });
-//         deterministic.run_until_parked();
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
+        // Deactivating the window saves the file.
+        cx.simulate_deactivation();
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 1));
+
+        // Autosave on focus change.
+        item.update(cx, |item, cx| {
+            cx.focus_self();
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         // Ensure autosave is prevented for deleted files also when closing the buffer.
-//         let _close_items = pane.update(cx, |pane, cx| {
-//             pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
-//         });
-//         deterministic.run_until_parked();
-//         assert!(window.has_pending_prompt(cx));
-//         item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
-//     }
+        // Blurring the item saves the file.
+        item.update(cx, |_, cx| cx.blur());
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 2));
 
-//     #[gpui::test]
-//     async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
+        // Deactivating the window still saves the file.
+        cx.simulate_activation();
+        item.update(cx, |item, cx| {
+            cx.focus_self();
+            item.is_dirty = true;
+        });
+        cx.simulate_deactivation();
 
-//         let fs = FakeFs::new(cx.background());
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        // Autosave after delay.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+                })
+            });
+            item.is_dirty = true;
+            cx.emit(ItemEvent::Edit);
+        });
 
-//         let item = window.build_view(cx, |cx| {
-//             TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-//         });
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
-//         let toolbar_notify_count = Rc::new(RefCell::new(0));
-
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.add_item(Box::new(item.clone()), cx);
-//             let toolbar_notification_count = toolbar_notify_count.clone();
-//             cx.observe(&toolbar, move |_, _, _| {
-//                 *toolbar_notification_count.borrow_mut() += 1
-//             })
-//             .detach();
-//         });
+        // Delay hasn't fully expired, so the file is still dirty and unsaved.
+        cx.executor().advance_clock(Duration::from_millis(250));
+        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
 
-//         pane.read_with(cx, |pane, _| {
-//             assert!(!pane.can_navigate_backward());
-//             assert!(!pane.can_navigate_forward());
-//         });
+        // After delay expires, the file is saved.
+        cx.executor().advance_clock(Duration::from_millis(250));
+        item.update(cx, |item, _| assert_eq!(item.save_count, 4));
 
-//         item.update(cx, |item, cx| {
-//             item.set_state("one".to_string(), cx);
-//         });
+        // Autosave on focus change, ensuring closing the tab counts as such.
+        item.update(cx, |item, cx| {
+            cx.update_global(|settings: &mut SettingsStore, cx| {
+                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
+                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                })
+            });
+            item.is_dirty = true;
+        });
 
-//         // Toolbar must be notified to re-render the navigation buttons
-//         assert_eq!(*toolbar_notify_count.borrow(), 1);
+        pane.update(cx, |pane, cx| {
+            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+        })
+        .await
+        .unwrap();
+        assert!(!cx.has_pending_prompt());
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+
+        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+        });
+        item.update(cx, |item, cx| {
+            item.project_items[0].update(cx, |item, _| {
+                item.entry_id = None;
+            });
+            item.is_dirty = true;
+            cx.blur();
+        });
+        cx.executor().run_until_parked();
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
 
-//         pane.read_with(cx, |pane, _| {
-//             assert!(pane.can_navigate_backward());
-//             assert!(!pane.can_navigate_forward());
-//         });
+        // Ensure autosave is prevented for deleted files also when closing the buffer.
+        let _close_items = pane.update(cx, |pane, cx| {
+            pane.close_items(cx, SaveIntent::Close, move |id| id == item_id)
+        });
+        cx.executor().run_until_parked();
+        assert!(cx.has_pending_prompt());
+        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
+    }
 
-//         workspace
-//             .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
-//             .await
-//             .unwrap();
+    #[gpui::test]
+    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
+        init_test(cx);
 
-//         assert_eq!(*toolbar_notify_count.borrow(), 3);
-//         pane.read_with(cx, |pane, _| {
-//             assert!(!pane.can_navigate_backward());
-//             assert!(pane.can_navigate_forward());
-//         });
-//     }
+        let fs = FakeFs::new(cx.executor());
 
-//     #[gpui::test]
-//     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
-//         let fs = FakeFs::new(cx.background());
+        let project = Project::test(fs, [], cx).await;
+        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
 
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
+        let item = cx.build_view(|cx| {
+            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
+        });
+        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+        let toolbar = pane.update(cx, |pane, _| pane.toolbar().clone());
+        let toolbar_notify_count = Rc::new(RefCell::new(0));
+
+        workspace.update(cx, |workspace, cx| {
+            workspace.add_item(Box::new(item.clone()), cx);
+            let toolbar_notification_count = toolbar_notify_count.clone();
+            cx.observe(&toolbar, move |_, _, _| {
+                *toolbar_notification_count.borrow_mut() += 1
+            })
+            .detach();
+        });
 
-//         let panel = workspace.update(cx, |workspace, cx| {
-//             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-//             workspace.add_panel(panel.clone(), cx);
+        pane.update(cx, |pane, _| {
+            assert!(!pane.can_navigate_backward());
+            assert!(!pane.can_navigate_forward());
+        });
 
-//             workspace
-//                 .right_dock()
-//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+        item.update(cx, |item, cx| {
+            item.set_state("one".to_string(), cx);
+        });
 
-//             panel
-//         });
+        // Toolbar must be notified to re-render the navigation buttons
+        assert_eq!(*toolbar_notify_count.borrow(), 1);
 
-//         let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
-//         pane.update(cx, |pane, cx| {
-//             let item = cx.build_view(|_| TestItem::new());
-//             pane.add_item(Box::new(item), true, true, None, cx);
-//         });
+        pane.update(cx, |pane, _| {
+            assert!(pane.can_navigate_backward());
+            assert!(!pane.can_navigate_forward());
+        });
 
-//         // Transfer focus from center to panel
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+        workspace
+            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
+            .await
+            .unwrap();
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+        assert_eq!(*toolbar_notify_count.borrow(), 2);
+        pane.update(cx, |pane, _| {
+            assert!(!pane.can_navigate_backward());
+            assert!(pane.can_navigate_forward());
+        });
+    }
 
-//         // Transfer focus from panel to center
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //     #[gpui::test]
+    //     async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
+    //         init_test(cx);
+    //         let fs = FakeFs::new(cx.executor());
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         let project = Project::test(fs, [], cx).await;
+    //         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
+    //         let workspace = window.root(cx);
 
-//         // Close the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx);
-//         });
+    //         let panel = workspace.update(cx, |workspace, cx| {
+    //             let panel = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+    //             workspace.add_panel(panel.clone(), cx);
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //             workspace
+    //                 .right_dock()
+    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
 
-//         // Open the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx);
-//         });
+    //             panel
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(!panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
+    //         pane.update(cx, |pane, cx| {
+    //             let item = cx.build_view(|_| TestItem::new(cx));
+    //             pane.add_item(Box::new(item), true, true, None, cx);
+    //         });
 
-//         // Focus and zoom panel
-//         panel.update(cx, |panel, cx| {
-//             cx.focus_self();
-//             panel.set_zoomed(true, cx)
-//         });
+    //         // Transfer focus from center to panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Transfer focus to the center closes the dock
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         // Transfer focus from panel to center
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Transferring focus back to the panel keeps it zoomed
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_panel_focus::<TestPanel>(cx);
-//         });
+    //         // Close the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         // Close the dock while it is zoomed
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
+    //         // Open the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx);
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(!workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(workspace.zoomed.is_none());
-//             assert!(!panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(!panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Opening the dock, when it's zoomed, retains focus
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
+    //         // Focus and zoom panel
+    //         panel.update(cx, |panel, cx| {
+    //             cx.focus_self();
+    //             panel.set_zoomed(true, cx)
+    //         });
 
-//         workspace.read_with(cx, |workspace, cx| {
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(panel.is_zoomed(cx));
-//             assert!(workspace.zoomed.is_some());
-//             assert!(panel.has_focus(cx));
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         // Unzoom and close the panel, zoom the active pane.
-//         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
-//         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
+    //         // Transfer focus to the center closes the dock
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//         // Opening a dock unzooms the pane.
-//         workspace.update(cx, |workspace, cx| {
-//             workspace.toggle_dock(DockPosition::Right, cx)
-//         });
-//         workspace.read_with(cx, |workspace, cx| {
-//             let pane = pane.read(cx);
-//             assert!(!pane.is_zoomed());
-//             assert!(!pane.has_focus());
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert!(workspace.zoomed.is_none());
-//         });
-//     }
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//     #[gpui::test]
-//     async fn test_panels(cx: &mut gpui::TestAppContext) {
-//         init_test(cx);
-//         let fs = FakeFs::new(cx.background());
-
-//         let project = Project::test(fs, [], cx).await;
-//         let window = cx.add_window(|cx| Workspace::test_new(project, cx));
-//         let workspace = window.root(cx);
-
-//         let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
-//             // Add panel_1 on the left, panel_2 on the right.
-//             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
-//             workspace.add_panel(panel_1.clone(), cx);
-//             workspace
-//                 .left_dock()
-//                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
-//             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
-//             workspace.add_panel(panel_2.clone(), cx);
-//             workspace
-//                 .right_dock()
-//                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-//             let left_dock = workspace.left_dock();
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(
-//                 left_dock.read(cx).active_panel_size(cx).unwrap(),
-//                 panel_1.size(cx)
-//             );
+    //         // Transferring focus back to the panel keeps it zoomed
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_panel_focus::<TestPanel>(cx);
+    //         });
 
-//             left_dock.update(cx, |left_dock, cx| {
-//                 left_dock.resize_active_panel(Some(1337.), cx)
-//             });
-//             assert_eq!(
-//                 workspace
-//                     .right_dock()
-//                     .read(cx)
-//                     .visible_panel()
-//                     .unwrap()
-//                     .id(),
-//                 panel_2.id()
-//             );
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//             (panel_1, panel_2)
-//         });
+    //         // Close the dock while it is zoomed
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
 
-//         // Move panel_1 to the right
-//         panel_1.update(cx, |panel_1, cx| {
-//             panel_1.set_position(DockPosition::Right, cx)
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(workspace.zoomed.is_none());
+    //             assert!(!panel.has_focus(cx));
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
-//             // Since it was the only panel on the left, the left dock should now be closed.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
-//             let right_dock = workspace.right_dock();
-//             assert_eq!(
-//                 right_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //         // Opening the dock, when it's zoomed, retains focus
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
 
-//             // Now we move panel_2Β to the left
-//             panel_2.set_position(DockPosition::Left, cx);
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(panel.is_zoomed(cx));
+    //             assert!(workspace.zoomed.is_some());
+    //             assert!(panel.has_focus(cx));
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_2 was not visible on the right, we don't open the left dock.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             // And the right dock is unaffected in it's displaying of panel_1
-//             assert!(workspace.right_dock().read(cx).is_open());
-//             assert_eq!(
-//                 workspace
-//                     .right_dock()
-//                     .read(cx)
-//                     .visible_panel()
-//                     .unwrap()
-//                     .id(),
-//                 panel_1.id()
-//             );
-//         });
+    //         // Unzoom and close the panel, zoom the active pane.
+    //         panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
+    //         pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
 
-//         // Move panel_1 back to the left
-//         panel_1.update(cx, |panel_1, cx| {
-//             panel_1.set_position(DockPosition::Left, cx)
-//         });
+    //         // Opening a dock unzooms the pane.
+    //         workspace.update(cx, |workspace, cx| {
+    //             workspace.toggle_dock(DockPosition::Right, cx)
+    //         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             let pane = pane.read(cx);
+    //             assert!(!pane.is_zoomed());
+    //             assert!(!pane.has_focus());
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert!(workspace.zoomed.is_none());
+    //         });
+    //     }
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
-//             // And right the dock should be closed as it no longer has any panels.
-//             assert!(!workspace.right_dock().read(cx).is_open());
+    //     #[gpui::test]
+    //     async fn test_panels(cx: &mut gpui::TestAppContext) {
+    //         init_test(cx);
+    //         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 (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
+    //             // Add panel_1 on the left, panel_2 on the right.
+    //             let panel_1 = cx.build_view(|_| TestPanel::new(DockPosition::Left));
+    //             workspace.add_panel(panel_1.clone(), cx);
+    //             workspace
+    //                 .left_dock()
+    //                 .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
+    //             let panel_2 = cx.build_view(|_| TestPanel::new(DockPosition::Right));
+    //             workspace.add_panel(panel_2.clone(), cx);
+    //             workspace
+    //                 .right_dock()
+    //                 .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
+
+    //             let left_dock = workspace.left_dock();
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(
+    //                 left_dock.read(cx).active_panel_size(cx).unwrap(),
+    //                 panel_1.size(cx)
+    //             );
+
+    //             left_dock.update(cx, |left_dock, cx| {
+    //                 left_dock.resize_active_panel(Some(1337.), cx)
+    //             });
+    //             assert_eq!(
+    //                 workspace
+    //                     .right_dock()
+    //                     .read(cx)
+    //                     .visible_panel()
+    //                     .unwrap()
+    //                     .id(),
+    //                 panel_2.id()
+    //             );
+
+    //             (panel_1, panel_2)
+    //         });
 
-//             // Now we move panel_1 to the bottom
-//             panel_1.set_position(DockPosition::Bottom, cx);
-//         });
+    //         // Move panel_1 to the right
+    //         panel_1.update(cx, |panel_1, cx| {
+    //             panel_1.set_position(DockPosition::Right, cx)
+    //         });
 
-//         workspace.update(cx, |workspace, cx| {
-//             // Since panel_1 was visible on the left, we close the left dock.
-//             assert!(!workspace.left_dock().read(cx).is_open());
-//             // The bottom dock is sized based on the panel's default size,
-//             // since the panel orientation changed from vertical to horizontal.
-//             let bottom_dock = workspace.bottom_dock();
-//             assert_eq!(
-//                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
-//                 panel_1.size(cx),
-//             );
-//             // Close bottom dock and move panel_1 back to the left.
-//             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
-//             panel_1.set_position(DockPosition::Left, cx);
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
+    //             // Since it was the only panel on the left, the left dock should now be closed.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             assert!(workspace.left_dock().read(cx).visible_panel().is_none());
+    //             let right_dock = workspace.right_dock();
+    //             assert_eq!(
+    //                 right_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+
+    //             // Now we move panel_2Β to the left
+    //             panel_2.set_position(DockPosition::Left, cx);
+    //         });
 
-//         // Emit activated event on panel 1
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_2 was not visible on the right, we don't open the left dock.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             // And the right dock is unaffected in it's displaying of panel_1
+    //             assert!(workspace.right_dock().read(cx).is_open());
+    //             assert_eq!(
+    //                 workspace
+    //                     .right_dock()
+    //                     .read(cx)
+    //                     .visible_panel()
+    //                     .unwrap()
+    //                     .id(),
+    //                 panel_1.id()
+    //             );
+    //         });
 
-//         // Now the left dock is open and panel_1 is active and focused.
-//         workspace.read_with(cx, |workspace, cx| {
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//             assert!(panel_1.is_focused(cx));
-//         });
+    //         // Move panel_1 back to the left
+    //         panel_1.update(cx, |panel_1, cx| {
+    //             panel_1.set_position(DockPosition::Left, cx)
+    //         });
 
-//         // Emit closed event on panel 2, which is not active
-//         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
+    //             // And right the dock should be closed as it no longer has any panels.
+    //             assert!(!workspace.right_dock().read(cx).is_open());
+
+    //             // Now we move panel_1 to the bottom
+    //             panel_1.set_position(DockPosition::Bottom, cx);
+    //         });
 
-//         // Wo don't close the left dock, because panel_2 wasn't the active panel
-//         workspace.read_with(cx, |workspace, cx| {
-//             let left_dock = workspace.left_dock();
-//             assert!(left_dock.read(cx).is_open());
-//             assert_eq!(
-//                 left_dock.read(cx).visible_panel().unwrap().id(),
-//                 panel_1.id()
-//             );
-//         });
+    //         workspace.update(cx, |workspace, cx| {
+    //             // Since panel_1 was visible on the left, we close the left dock.
+    //             assert!(!workspace.left_dock().read(cx).is_open());
+    //             // The bottom dock is sized based on the panel's default size,
+    //             // since the panel orientation changed from vertical to horizontal.
+    //             let bottom_dock = workspace.bottom_dock();
+    //             assert_eq!(
+    //                 bottom_dock.read(cx).active_panel_size(cx).unwrap(),
+    //                 panel_1.size(cx),
+    //             );
+    //             // Close bottom dock and move panel_1 back to the left.
+    //             bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
+    //             panel_1.set_position(DockPosition::Left, cx);
+    //         });
 
-//         // Emitting a ZoomIn event shows the panel as zoomed.
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-//         });
+    //         // Emit activated event on panel 1
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
+
+    //         // Now the left dock is open and panel_1 is active and focused.
+    //         workspace.update(cx, |workspace, cx| {
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //             assert!(panel_1.is_focused(cx));
+    //         });
 
-//         // Move panel to another dock while it is zoomed
-//         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // Emit closed event on panel 2, which is not active
+    //         panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+    //         // Wo don't close the left dock, because panel_2 wasn't the active panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             let left_dock = workspace.left_dock();
+    //             assert!(left_dock.read(cx).is_open());
+    //             assert_eq!(
+    //                 left_dock.read(cx).visible_panel().unwrap().id(),
+    //                 panel_1.id()
+    //             );
+    //         });
 
-//         // If focus is transferred to another view that's not a panel or another pane, we still show
-//         // the panel as zoomed.
-//         let focus_receiver = window.build_view(cx, |_| EmptyView);
-//         focus_receiver.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // Emitting a ZoomIn event shows the panel as zoomed.
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
+    //         });
 
-//         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
-//         workspace.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // Move panel to another dock while it is zoomed
+    //         panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // If focus is transferred again to another view that's not a panel or a pane, we won't
-//         // show the panel as zoomed because it wasn't zoomed before.
-//         focus_receiver.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // If focus is transferred to another view that's not a panel or another pane, we still show
+    //         // the panel as zoomed.
+    //         let focus_receiver = cx.build_view(|_| EmptyView);
+    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // When focus is transferred back to the panel, it is zoomed again.
-//         panel_1.update(cx, |_, cx| cx.focus_self());
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
-//             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-//         });
+    //         // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
+    //         workspace.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // Emitting a ZoomOut event unzooms the panel.
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
-//         workspace.read_with(cx, |workspace, _| {
-//             assert_eq!(workspace.zoomed, None);
-//             assert_eq!(workspace.zoomed_position, None);
-//         });
+    //         // If focus is transferred again to another view that's not a panel or a pane, we won't
+    //         // show the panel as zoomed because it wasn't zoomed before.
+    //         focus_receiver.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//         // Emit closed event on panel 1, which is active
-//         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+    //         // When focus is transferred back to the panel, it is zoomed again.
+    //         panel_1.update(cx, |_, cx| cx.focus_self());
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
+    //             assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
+    //         });
 
-//         // Now the left dock is closed, because panel_1 was the active panel
-//         workspace.read_with(cx, |workspace, cx| {
-//             let right_dock = workspace.right_dock();
-//             assert!(!right_dock.read(cx).is_open());
-//         });
-//     }
+    //         // Emitting a ZoomOut event unzooms the panel.
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
+    //         workspace.update(cx, |workspace, _| {
+    //             assert_eq!(workspace.zoomed, None);
+    //             assert_eq!(workspace.zoomed_position, None);
+    //         });
 
-//     pub fn init_test(cx: &mut TestAppContext) {
-//         cx.foreground().forbid_parking();
-//         cx.update(|cx| {
-//             cx.set_global(SettingsStore::test(cx));
-//             theme::init((), cx);
-//             language::init(cx);
-//             crate::init_settings(cx);
-//             Project::init_settings(cx);
-//         });
-//     }
-// }
+    //         // Emit closed event on panel 1, which is active
+    //         panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
+
+    //         // Now the left dock is closed, because panel_1 was the active panel
+    //         workspace.update(cx, |workspace, cx| {
+    //             let right_dock = workspace.right_dock();
+    //             assert!(!right_dock.read(cx).is_open());
+    //         });
+    //     }
+
+    pub fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
+            theme::init(theme::LoadThemes::JustBase, cx);
+            language::init(cx);
+            crate::init_settings(cx);
+            Project::init_settings(cx);
+        });
+    }
+}

crates/zed2/src/main.rs πŸ”—

@@ -746,9 +746,9 @@ fn watch_file_types(fs: Arc<dyn fs::Fs>, cx: &mut AppContext) {
 }
 
 #[cfg(not(debug_assertions))]
-async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
+async fn watch_languages(_: Arc<dyn fs::Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
     None
 }
 
 #[cfg(not(debug_assertions))]
-fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
+fn watch_file_types(_fs: Arc<dyn fs::Fs>, _cx: &mut AppContext) {}

crates/zed2/src/zed2.rs πŸ”—

@@ -148,7 +148,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
         //     let feedback_button = cx.add_view(|_| {
         //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
         //     });
-        //     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+        let cursor_position = cx.build_view(|_| editor::items::CursorPosition::new());
         workspace.status_bar().update(cx, |status_bar, cx| {
             status_bar.add_left_item(diagnostic_summary, cx);
             status_bar.add_left_item(activity_indicator, cx);
@@ -157,7 +157,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             // status_bar.add_right_item(copilot, cx);
             // status_bar.add_right_item(active_buffer_language, cx);
             // status_bar.add_right_item(vim_mode_indicator, cx);
-            // status_bar.add_right_item(cursor_position, cx);
+            status_bar.add_right_item(cursor_position, cx);
         });
 
         auto_update::notify_of_any_new_update(cx);