Remove 2 suffix for workspace

Max Brunsfeld and Mikayla created

Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

Cargo.lock                                          |  100 
Cargo.toml                                          |    2 
crates/activity_indicator/Cargo.toml                |    2 
crates/assistant2/Cargo.toml                        |    2 
crates/auto_update/Cargo.toml                       |    2 
crates/breadcrumbs/Cargo.toml                       |    4 
crates/collab2/Cargo.toml                           |    2 
crates/collab_ui/Cargo.toml                         |    4 
crates/command_palette/Cargo.toml                   |    4 
crates/command_palette2/Cargo.toml                  |    2 
crates/copilot_button/Cargo.toml                    |    2 
crates/diagnostics/Cargo.toml                       |    4 
crates/editor/Cargo.toml                            |    4 
crates/feedback/Cargo.toml                          |    2 
crates/file_finder/Cargo.toml                       |    4 
crates/go_to_line/Cargo.toml                        |    2 
crates/journal2/Cargo.toml                          |    2 
crates/journal2/src/journal2.rs                     |    4 
crates/language_selector/Cargo.toml                 |    2 
crates/language_tools/Cargo.toml                    |    2 
crates/outline/Cargo.toml                           |    2 
crates/picker/Cargo.toml                            |    2 
crates/project_panel/Cargo.toml                     |    4 
crates/project_symbols/Cargo.toml                   |   14 
crates/quick_action_bar/Cargo.toml                  |    4 
crates/recent_projects/Cargo.toml                   |    2 
crates/search/Cargo.toml                            |    4 
crates/semantic_index2/Cargo.toml                   |    4 
crates/terminal_view/Cargo.toml                     |    4 
crates/theme_selector/Cargo.toml                    |    2 
crates/vcs_menu/Cargo.toml                          |    2 
crates/vim/Cargo.toml                               |    4 
crates/welcome/Cargo.toml                           |    2 
crates/workspace/Cargo.toml                         |   42 
crates/workspace/src/dock.rs                        |  810 +-
crates/workspace/src/item.rs                        |  521 
crates/workspace/src/modal_layer.rs                 |    0 
crates/workspace/src/notifications.rs               |  393 
crates/workspace/src/pane.rs                        |  710 +
crates/workspace/src/pane/dragged_item_receiver.rs  |    7 
crates/workspace/src/pane_group.rs                  |  990 +-
crates/workspace/src/persistence.rs                 |   37 
crates/workspace/src/persistence/model.rs           |   39 
crates/workspace/src/searchable.rs                  |   61 
crates/workspace/src/shared_screen.rs               |  105 
crates/workspace/src/status_bar.rs                  |  195 
crates/workspace/src/toolbar.rs                     |  334 
crates/workspace/src/workspace.rs                   |  770 -
crates/workspace/src/workspace_settings.rs          |    6 
crates/workspace/test.db                            |    0 
crates/workspace2/Cargo.toml                        |   66 
crates/workspace2/src/dock.rs                       |  783 --
crates/workspace2/src/item.rs                       | 1070 ---
crates/workspace2/src/notifications.rs              |  395 -
crates/workspace2/src/pane.rs                       | 2759 -------
crates/workspace2/src/pane/dragged_item_receiver.rs |  239 
crates/workspace2/src/pane_group.rs                 |  865 --
crates/workspace2/src/persistence.rs                |  973 --
crates/workspace2/src/persistence/model.rs          |  335 
crates/workspace2/src/searchable.rs                 |  277 
crates/workspace2/src/shared_screen.rs              |  128 
crates/workspace2/src/status_bar.rs                 |  194 
crates/workspace2/src/toolbar.rs                    |  321 
crates/workspace2/src/workspace2.rs                 | 5277 ---------------
crates/workspace2/src/workspace_settings.rs         |   56 
crates/zed/Cargo.toml                               |    2 
66 files changed, 2,432 insertions(+), 16,530 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -18,7 +18,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -403,7 +403,7 @@ dependencies = [
  "ui2",
  "util",
  "uuid 1.4.1",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -770,7 +770,7 @@ dependencies = [
  "tempdir",
  "theme2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -1099,7 +1099,7 @@ dependencies = [
  "settings2",
  "theme2",
  "ui2",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -1767,7 +1767,7 @@ dependencies = [
  "unindent",
  "util",
  "uuid 1.4.1",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -1812,7 +1812,7 @@ dependencies = [
  "ui2",
  "util",
  "vcs_menu",
- "workspace2",
+ "workspace",
  "zed_actions2",
 ]
 
@@ -1867,7 +1867,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
  "zed_actions2",
 ]
 
@@ -1986,7 +1986,7 @@ dependencies = [
  "smol",
  "theme2",
  "util",
- "workspace2",
+ "workspace",
  "zed_actions2",
 ]
 
@@ -2520,7 +2520,7 @@ dependencies = [
  "ui2",
  "unindent",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -2706,7 +2706,7 @@ dependencies = [
  "ui2",
  "unindent",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -2924,7 +2924,7 @@ dependencies = [
  "ui2",
  "urlencoding",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -2949,7 +2949,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -3497,7 +3497,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -4223,7 +4223,7 @@ dependencies = [
  "settings2",
  "shellexpand",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -4417,7 +4417,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -4441,7 +4441,7 @@ dependencies = [
  "ui2",
  "unindent",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -5680,7 +5680,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -5901,7 +5901,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -6330,7 +6330,7 @@ dependencies = [
  "ui2",
  "unicase",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -6353,7 +6353,7 @@ dependencies = [
  "text2",
  "theme2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -6521,7 +6521,7 @@ dependencies = [
  "gpui2",
  "search",
  "ui2",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -6703,7 +6703,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -7550,7 +7550,7 @@ dependencies = [
  "ui2",
  "unindent",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -7678,7 +7678,7 @@ dependencies = [
  "tree-sitter-typescript",
  "unindent",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -8843,7 +8843,7 @@ dependencies = [
  "thiserror",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -8980,7 +8980,7 @@ dependencies = [
  "theme2",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -10044,7 +10044,7 @@ dependencies = [
  "picker",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -10083,7 +10083,7 @@ dependencies = [
  "tokio",
  "ui2",
  "util",
- "workspace2",
+ "workspace",
  "zed_actions2",
 ]
 
@@ -10497,7 +10497,7 @@ dependencies = [
  "ui2",
  "util",
  "vim",
- "workspace2",
+ "workspace",
 ]
 
 [[package]]
@@ -10742,46 +10742,6 @@ dependencies = [
 [[package]]
 name = "workspace"
 version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-recursion 1.0.5",
- "bincode",
- "call",
- "client",
- "collections",
- "context_menu",
- "db",
- "drag_and_drop",
- "env_logger",
- "fs",
- "futures 0.3.28",
- "gpui",
- "indoc",
- "install_cli",
- "itertools 0.10.5",
- "language",
- "lazy_static",
- "log",
- "menu",
- "node_runtime",
- "parking_lot 0.11.2",
- "postage",
- "project",
- "schemars",
- "serde",
- "serde_derive",
- "serde_json",
- "settings",
- "smallvec",
- "terminal",
- "theme",
- "util",
- "uuid 1.4.1",
-]
-
-[[package]]
-name = "workspace2"
-version = "0.1.0"
 dependencies = [
  "anyhow",
  "async-recursion 1.0.5",
@@ -11021,7 +10981,7 @@ dependencies = [
  "uuid 1.4.1",
  "vim",
  "welcome",
- "workspace2",
+ "workspace",
  "zed_actions2",
 ]
 

Cargo.toml πŸ”—

@@ -105,7 +105,7 @@ members = [
     "crates/story",
     "crates/vim",
     "crates/vcs_menu",
-    "crates/workspace2",
+    "crates/workspace",
     "crates/welcome",
     "crates/xtask",
     "crates/zed",

crates/activity_indicator/Cargo.toml πŸ”—

@@ -18,7 +18,7 @@ settings = { path = "../settings2", package = "settings2" }
 ui = { path = "../ui2", package = "ui2" }
 util = { path = "../util" }
 theme = { path = "../theme2", package = "theme2" }
-workspace = { path = "../workspace2", package = "workspace2" }
+workspace = { path = "../workspace", package = "workspace" }
 
 anyhow.workspace = true
 futures.workspace = true

crates/assistant2/Cargo.toml πŸ”—

@@ -25,7 +25,7 @@ settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 
 uuid.workspace = true
 log.workspace = true

crates/auto_update/Cargo.toml πŸ”—

@@ -16,7 +16,7 @@ menu = { package = "menu2", path = "../menu2" }
 project = { package = "project2", path = "../project2" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 util = { path = "../util" }
 anyhow.workspace = true
 isahc.workspace = true

crates/breadcrumbs/Cargo.toml πŸ”—

@@ -18,11 +18,11 @@ project = { package = "project2", path = "../project2" }
 search = { path = "../search" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 outline = { path = "../outline" }
 itertools = "0.10"
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }

crates/collab2/Cargo.toml πŸ”—

@@ -79,7 +79,7 @@ project = { package = "project2", path = "../project2", features = ["test-suppor
 rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 
 collab_ui = { path = "../collab_ui", features = ["test-support"] }
 

crates/collab_ui/Cargo.toml πŸ”—

@@ -50,7 +50,7 @@ theme_selector = { path = "../theme_selector" }
 vcs_menu = { path = "../vcs_menu" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 zed-actions = { package="zed_actions2", path = "../zed_actions2"}
 
 anyhow.workspace = true
@@ -75,7 +75,7 @@ project = { package = "project2", path = "../project2", features = ["test-suppor
 rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 
 pretty_assertions.workspace = true
 tree-sitter-markdown.workspace = true

crates/command_palette/Cargo.toml πŸ”—

@@ -19,7 +19,7 @@ settings = { package = "settings2", path = "../settings2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package="workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 zed_actions = { package = "zed_actions2", path = "../zed_actions2" }
 anyhow.workspace = true
 serde.workspace = true
@@ -32,6 +32,6 @@ project = { package="project2", path = "../project2", features = ["test-support"
 menu = { package = "menu2", path = "../menu2" }
 go_to_line = { path = "../go_to_line" }
 serde_json.workspace = true
-workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 ctor.workspace = true
 env_logger.workspace = true

crates/command_palette2/Cargo.toml πŸ”—

@@ -31,6 +31,6 @@ project = { package="project2", path = "../project2", features = ["test-support"
 menu = { package = "menu2", path = "../menu2" }
 go_to_line = { package = "go_to_line2", path = "../go_to_line2" }
 serde_json.workspace = true
-workspace = { package="workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 ctor.workspace = true
 env_logger.workspace = true

crates/copilot_button/Cargo.toml πŸ”—

@@ -18,7 +18,7 @@ language = { package = "language2", path = "../language2" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = {path = "../workspace" }
 anyhow.workspace = true
 smol.workspace = true
 futures.workspace = true

crates/diagnostics/Cargo.toml πŸ”—

@@ -19,7 +19,7 @@ project = { package = "project2", path = "../project2" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = {path = "../workspace" }
 
 log.workspace = true
 anyhow.workspace = true
@@ -36,7 +36,7 @@ editor = { path = "../editor", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
 lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = {path = "../workspace", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
 
 serde_json.workspace = true

crates/editor/Cargo.toml πŸ”—

@@ -46,7 +46,7 @@ theme = { package="theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
 sqlez = { path = "../sqlez" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 
 aho-corasick = "1.1"
 anyhow.workspace = true
@@ -80,7 +80,7 @@ gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 project = { package = "project2", path = "../project2", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
 
 ctor.workspace = true

crates/feedback/Cargo.toml πŸ”—

@@ -23,7 +23,7 @@ settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2"}
+workspace = { path = "../workspace"}
 
 bitflags = "2.4.1"
 human_bytes = "0.4.1"

crates/file_finder/Cargo.toml πŸ”—

@@ -21,7 +21,7 @@ text = { package = "text2", path = "../text2" }
 util = { path = "../util" }
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 postage.workspace = true
 serde.workspace = true
 
@@ -29,7 +29,7 @@ serde.workspace = true
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
 
 serde_json.workspace = true

crates/go_to_line/Cargo.toml πŸ”—

@@ -15,7 +15,7 @@ menu = { package = "menu2", path = "../menu2" }
 serde.workspace = true
 settings = { package = "settings2", path = "../settings2" }
 text = { package = "text2", path = "../text2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 postage.workspace = true
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }

crates/journal2/Cargo.toml πŸ”—

@@ -12,7 +12,7 @@ doctest = false
 editor = { path = "../editor" }
 gpui = { package = "gpui2", path = "../gpui2" }
 util = { path = "../util" }
-workspace2 = { path = "../workspace2" }
+workspace = { path = "../workspace" }
 settings2 = { path = "../settings2" }
 
 anyhow.workspace = true

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

@@ -9,7 +9,7 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use workspace2::{AppState, Workspace};
+use workspace::{AppState, Workspace};
 
 actions!(journal, [NewJournalEntry]);
 
@@ -93,7 +93,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.spawn(|mut cx| async move {
         let (journal_dir, entry_path) = create_entry.await?;
         let (workspace, _) = cx
-            .update(|cx| workspace2::open_paths(&[journal_dir], &app_state, None, cx))?
+            .update(|cx| workspace::open_paths(&[journal_dir], &app_state, None, cx))?
             .await?;
 
         let _opened = workspace

crates/language_selector/Cargo.toml πŸ”—

@@ -19,7 +19,7 @@ theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
 settings = { package = "settings2", path = "../settings2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 anyhow.workspace = true
 
 [dev-dependencies]

crates/language_tools/Cargo.toml πŸ”—

@@ -15,7 +15,7 @@ settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 language = { package = "language2", path = "../language2" }
 project = { package = "project2", path = "../project2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 gpui = { package = "gpui2", path = "../gpui2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }

crates/outline/Cargo.toml πŸ”—

@@ -18,7 +18,7 @@ picker = { path = "../picker" }
 settings = { package = "settings2", path = "../settings2" }
 text = { package = "text2", path = "../text2" }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 util = { path = "../util" }
 
 ordered-float.workspace = true

crates/picker/Cargo.toml πŸ”—

@@ -16,7 +16,7 @@ menu = { package = "menu2", path = "../menu2" }
 settings = { package = "settings2", path = "../settings2" }
 util = { path = "../util" }
 theme = { package = "theme2", path = "../theme2" }
-workspace = { package = "workspace2", path = "../workspace2"}
+workspace = { path = "../workspace"}
 
 parking_lot.workspace = true
 

crates/project_panel/Cargo.toml πŸ”—

@@ -20,7 +20,7 @@ settings = { path = "../settings2", package = "settings2" }
 theme = { path = "../theme2", package = "theme2" }
 ui = { path = "../ui2", package = "ui2" }
 util = { path = "../util" }
-workspace = { path = "../workspace2", package = "workspace2" }
+workspace = { path = "../workspace", package = "workspace" }
 anyhow.workspace = true
 postage.workspace = true
 futures.workspace = true
@@ -37,5 +37,5 @@ client = { path = "../client2", package = "client2", features = ["test-support"]
 language = { path = "../language2", package = "language2", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui2", package = "gpui2", features = ["test-support"] }
-workspace = { path = "../workspace2", package = "workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 serde_json.workspace = true

crates/project_symbols/Cargo.toml πŸ”—

@@ -10,13 +10,13 @@ doctest = false
 
 [dependencies]
 editor = { path = "../editor" }
-fuzzy = {package = "fuzzy2",  path = "../fuzzy2" }
-gpui = {package = "gpui2",  path = "../gpui2" }
-picker = {path = "../picker" }
+fuzzy = { package = "fuzzy2",  path = "../fuzzy2" }
+gpui = { package = "gpui2",  path = "../gpui2" }
+picker = { path = "../picker" }
 project = { package = "project2", path = "../project2" }
-text = {package = "text2",  path = "../text2" }
-settings = {package = "settings2",  path = "../settings2" }
-workspace = {package = "workspace2",  path = "../workspace2" }
+text = { package = "text2",  path = "../text2" }
+settings = { package = "settings2",  path = "../settings2" }
+workspace = { path = "../workspace" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }
 
@@ -34,4 +34,4 @@ language = { package = "language2", path = "../language2", features = ["test-sup
 lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
 project = { package = "project2", path = "../project2", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
-workspace = { package = "workspace2",  path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }

crates/quick_action_bar/Cargo.toml πŸ”—

@@ -13,10 +13,10 @@ assistant = { package = "assistant2", path = "../assistant2" }
 editor = { path = "../editor" }
 gpui = { package = "gpui2", path = "../gpui2" }
 search = { path = "../search" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 ui = { package = "ui2", path = "../ui2" }
 
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }

crates/recent_projects/Cargo.toml πŸ”—

@@ -19,7 +19,7 @@ text = { package = "text2", path = "../text2" }
 util = { path = "../util"}
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 
 futures.workspace = true
 ordered-float.workspace = true

crates/search/Cargo.toml πŸ”—

@@ -20,7 +20,7 @@ settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }
 ui = {package = "ui2", path = "../ui2"}
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
 anyhow.workspace = true
 futures.workspace = true
@@ -36,5 +36,5 @@ client = { package = "client2", path = "../client2", features = ["test-support"]
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 unindent.workspace = true

crates/semantic_index2/Cargo.toml πŸ”—

@@ -14,7 +14,7 @@ collections = { path = "../collections" }
 gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 project = { package = "project2", path = "../project2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 util = { path = "../util" }
 rpc = { package = "rpc2", path = "../rpc2" }
 settings = { package = "settings2", path = "../settings2" }
@@ -45,7 +45,7 @@ gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 language = { package = "language2", path = "../language2", features = ["test-support"] }
 project = { package = "project2", path = "../project2", features = ["test-support"] }
 rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2", features = ["test-support"]}
 rust-embed = { version = "8.0", features = ["include-exclude"] }
 client = { package = "client2", path = "../client2" }

crates/terminal_view/Cargo.toml πŸ”—

@@ -17,7 +17,7 @@ project = { package = "project2", path = "../project2" }
 settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 db = { package = "db2", path = "../db2" }
 procinfo = { git = "https://github.com/zed-industries/wezterm", rev = "5cd757e5f2eb039ed0c6bb6512223e69d5efc64d", default-features = false }
 terminal = { package = "terminal2", path = "../terminal2" }
@@ -42,5 +42,5 @@ editor = { path = "../editor", features = ["test-support"] }
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
 client = { package = "client2", path = "../client2", features = ["test-support"]}
 project = { package = "project2", path = "../project2", features = ["test-support"]}
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 rand.workspace = true

crates/theme_selector/Cargo.toml πŸ”—

@@ -20,7 +20,7 @@ settings = { package = "settings2", path = "../settings2" }
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2" }
 util = { path = "../util" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true

crates/vcs_menu/Cargo.toml πŸ”—

@@ -12,6 +12,6 @@ gpui = {package = "gpui2", path = "../gpui2"}
 picker = {path = "../picker"}
 util = {path = "../util"}
 ui = {package = "ui2", path = "../ui2"}
-workspace = {package = "workspace2", path = "../workspace2"}
+workspace = { path = "../workspace" }
 
 anyhow.workspace = true

crates/vim/Cargo.toml πŸ”—

@@ -31,7 +31,7 @@ gpui = { package = "gpui2", path = "../gpui2" }
 language = { package = "language2", path = "../language2" }
 search = { path = "../search" }
 settings = { package = "settings2", path = "../settings2" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 theme = { package = "theme2", path = "../theme2" }
 ui = { package = "ui2", path = "../ui2"}
 diagnostics = { path = "../diagnostics" }
@@ -48,6 +48,6 @@ language = { package = "language2", path = "../language2", features = ["test-sup
 project = { package = "project2", path = "../project2", features = ["test-support"] }
 util = { path = "../util", features = ["test-support"] }
 settings = { package = "settings2", path = "../settings2" }
-workspace = { package = "workspace2", path = "../workspace2", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
 theme = { package = "theme2", path = "../theme2", features = ["test-support"] }
 lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }

crates/welcome/Cargo.toml πŸ”—

@@ -25,7 +25,7 @@ theme = { package = "theme2", path = "../theme2" }
 theme_selector = { path = "../theme_selector" }
 util = { path = "../util" }
 picker = { path = "../picker" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 vim = { path = "../vim" }
 
 anyhow.workspace = true

crates/workspace/Cargo.toml πŸ”—

@@ -19,23 +19,23 @@ test-support = [
 ]
 
 [dependencies]
-db = { path = "../db" }
-call = { path = "../call" }
-client = { path = "../client" }
+db = { path = "../db2", package = "db2" }
+call = { path = "../call2", package = "call2" }
+client = { path = "../client2", package = "client2" }
 collections = { path = "../collections" }
-context_menu = { path = "../context_menu" }
-drag_and_drop = { path = "../drag_and_drop" }
-fs = { path = "../fs" }
-gpui = { path = "../gpui" }
-install_cli = { path = "../install_cli" }
-language = { path = "../language" }
-menu = { path = "../menu" }
+# context_menu = { path = "../context_menu" }
+fs = { path = "../fs2", package = "fs2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+install_cli = { path = "../install_cli2", package = "install_cli2" }
+language = { path = "../language2", package = "language2" }
+#menu = { path = "../menu" }
 node_runtime = { path = "../node_runtime" }
-project = { path = "../project" }
-settings = { path = "../settings" }
-terminal = { path = "../terminal" }
-theme = { path = "../theme" }
+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" }
 
 async-recursion = "1.0.0"
 itertools = "0.10"
@@ -54,13 +54,13 @@ smallvec.workspace = true
 uuid.workspace = true
 
 [dev-dependencies]
-call = { path = "../call", features = ["test-support"] }
-client = { path = "../client", features = ["test-support"] }
-gpui = { path = "../gpui", features = ["test-support"] }
-project = { path = "../project", features = ["test-support"] }
-settings = { path = "../settings", features = ["test-support"] }
-fs = { path = "../fs", features = ["test-support"] }
-db = { path = "../db", 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/workspace/src/dock.rs πŸ”—

@@ -1,70 +1,76 @@
-use crate::{StatusItemView, Workspace, WorkspaceBounds};
-use context_menu::{ContextMenu, ContextMenuItem};
+use crate::DraggedDock;
+use crate::{status_bar::StatusItemView, Workspace};
 use gpui::{
-    elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyViewHandle, AppContext,
-    Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
+    EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
+    SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use std::rc::Rc;
-use theme::ThemeSettings;
+use std::sync::Arc;
+use ui::{h_stack, ContextMenu, IconButton, Tooltip};
+use ui::{prelude::*, right_click_menu};
+
+const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
+
+pub enum PanelEvent {
+    ChangePosition,
+    ZoomIn,
+    ZoomOut,
+    Activate,
+    Close,
+    Focus,
+}
 
-pub trait Panel: View {
+pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
+    fn persistent_name() -> &'static str;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition) -> bool;
     fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
-    fn size(&self, cx: &WindowContext) -> f32;
-    fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>);
-    fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>;
-    fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>);
+    fn size(&self, cx: &WindowContext) -> Pixels;
+    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
+    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
+    fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
+    fn toggle_action(&self) -> Box<dyn Action>;
     fn icon_label(&self, _: &WindowContext) -> Option<String> {
         None
     }
-    fn should_change_position_on_event(_: &Self::Event) -> bool;
-    fn should_zoom_in_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn should_zoom_out_on_event(_: &Self::Event) -> bool {
-        false
-    }
     fn is_zoomed(&self, _cx: &WindowContext) -> bool {
         false
     }
     fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
     fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
-    fn should_activate_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn should_close_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn has_focus(&self, cx: &WindowContext) -> bool;
-    fn is_focus_event(_: &Self::Event) -> bool;
 }
 
-pub trait PanelHandle {
-    fn id(&self) -> usize;
+pub trait PanelHandle: Send + Sync {
+    fn panel_id(&self) -> EntityId;
+    fn persistent_name(&self) -> &'static str;
     fn position(&self, cx: &WindowContext) -> DockPosition;
     fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
     fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
     fn is_zoomed(&self, cx: &WindowContext) -> bool;
     fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
     fn set_active(&self, active: bool, cx: &mut WindowContext);
-    fn size(&self, cx: &WindowContext) -> f32;
-    fn set_size(&self, size: Option<f32>, cx: &mut WindowContext);
-    fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>;
-    fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option<Box<dyn Action>>);
+    fn size(&self, cx: &WindowContext) -> Pixels;
+    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
+    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
+    fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
+    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
     fn icon_label(&self, cx: &WindowContext) -> Option<String>;
-    fn has_focus(&self, cx: &WindowContext) -> bool;
-    fn as_any(&self) -> &AnyViewHandle;
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
+    fn to_any(&self) -> AnyView;
 }
 
-impl<T> PanelHandle for ViewHandle<T>
+impl<T> PanelHandle for View<T>
 where
     T: Panel,
 {
-    fn id(&self) -> usize {
-        self.id()
+    fn panel_id(&self) -> EntityId {
+        Entity::entity_id(self)
+    }
+
+    fn persistent_name(&self) -> &'static str {
+        T::persistent_name()
     }
 
     fn position(&self, cx: &WindowContext) -> DockPosition {
@@ -79,14 +85,6 @@ where
         self.update(cx, |this, cx| this.set_position(position, cx))
     }
 
-    fn size(&self, cx: &WindowContext) -> f32 {
-        self.read(cx).size(cx)
-    }
-
-    fn set_size(&self, size: Option<f32>, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.set_size(size, cx))
-    }
-
     fn is_zoomed(&self, cx: &WindowContext) -> bool {
         self.read(cx).is_zoomed(cx)
     }
@@ -99,30 +97,42 @@ where
         self.update(cx, |this, cx| this.set_active(active, cx))
     }
 
-    fn icon_path(&self, cx: &WindowContext) -> Option<&'static str> {
-        self.read(cx).icon_path(cx)
+    fn size(&self, cx: &WindowContext) -> Pixels {
+        self.read(cx).size(cx)
     }
 
-    fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option<Box<dyn Action>>) {
-        self.read(cx).icon_tooltip()
+    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext) {
+        self.update(cx, |this, cx| this.set_size(size, cx))
+    }
+
+    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
+        self.read(cx).icon(cx)
+    }
+
+    fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str> {
+        self.read(cx).icon_tooltip(cx)
+    }
+
+    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action> {
+        self.read(cx).toggle_action()
     }
 
     fn icon_label(&self, cx: &WindowContext) -> Option<String> {
         self.read(cx).icon_label(cx)
     }
 
-    fn has_focus(&self, cx: &WindowContext) -> bool {
-        self.read(cx).has_focus(cx)
+    fn to_any(&self) -> AnyView {
+        self.clone().into()
     }
 
-    fn as_any(&self) -> &AnyViewHandle {
-        self
+    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
+        self.read(cx).focus_handle(cx).clone()
     }
 }
 
-impl From<&dyn PanelHandle> for AnyViewHandle {
+impl From<&dyn PanelHandle> for AnyView {
     fn from(val: &dyn PanelHandle) -> Self {
-        val.as_any().clone()
+        val.to_any()
     }
 }
 
@@ -131,6 +141,14 @@ pub struct Dock {
     panel_entries: Vec<PanelEntry>,
     is_open: bool,
     active_panel_index: usize,
+    focus_handle: FocusHandle,
+    _focus_subscription: Subscription,
+}
+
+impl FocusableView for Dock {
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus_handle.clone()
+    }
 }
 
 #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
@@ -150,13 +168,14 @@ impl DockPosition {
         }
     }
 
-    fn to_resize_handle_side(self) -> HandleSide {
-        match self {
-            Self::Left => HandleSide::Right,
-            Self::Bottom => HandleSide::Top,
-            Self::Right => HandleSide::Left,
-        }
-    }
+    // todo!()
+    // fn to_resize_handle_side(self) -> HandleSide {
+    //     match self {
+    //         Self::Left => HandleSide::Right,
+    //         Self::Bottom => HandleSide::Top,
+    //         Self::Right => HandleSide::Left,
+    //     }
+    // }
 
     pub fn axis(&self) -> Axis {
         match self {
@@ -167,24 +186,54 @@ impl DockPosition {
 }
 
 struct PanelEntry {
-    panel: Rc<dyn PanelHandle>,
-    context_menu: ViewHandle<ContextMenu>,
+    panel: Arc<dyn PanelHandle>,
+    // todo!()
+    // context_menu: View<ContextMenu>,
     _subscriptions: [Subscription; 2],
 }
 
 pub struct PanelButtons {
-    dock: ViewHandle<Dock>,
-    workspace: WeakViewHandle<Workspace>,
+    dock: View<Dock>,
 }
 
 impl Dock {
-    pub fn new(position: DockPosition) -> Self {
-        Self {
-            position,
-            panel_entries: Default::default(),
-            active_panel_index: 0,
-            is_open: false,
-        }
+    pub fn new(position: DockPosition, cx: &mut ViewContext<Workspace>) -> View<Self> {
+        let focus_handle = cx.focus_handle();
+
+        let dock = cx.new_view(|cx: &mut ViewContext<Self>| {
+            let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
+                if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
+                    active_entry.panel.focus_handle(cx).focus(cx)
+                }
+            });
+            Self {
+                position,
+                panel_entries: Default::default(),
+                active_panel_index: 0,
+                is_open: false,
+                focus_handle: focus_handle.clone(),
+                _focus_subscription: focus_subscription,
+            }
+        });
+
+        cx.observe(&dock, move |workspace, dock, cx| {
+            if dock.read(cx).is_open() {
+                if let Some(panel) = dock.read(cx).active_panel() {
+                    if panel.is_zoomed(cx) {
+                        workspace.zoomed = Some(panel.to_any().downgrade());
+                        workspace.zoomed_position = Some(position);
+                        return;
+                    }
+                }
+            }
+            if workspace.zoomed_position == Some(position) {
+                workspace.zoomed = None;
+                workspace.zoomed_position = None;
+            }
+        })
+        .detach();
+
+        dock
     }
 
     pub fn position(&self) -> DockPosition {
@@ -195,28 +244,32 @@ impl Dock {
         self.is_open
     }
 
-    pub fn has_focus(&self, cx: &WindowContext) -> bool {
-        self.visible_panel()
-            .map_or(false, |panel| panel.has_focus(cx))
-    }
+    // todo!()
+    //     pub fn has_focus(&self, cx: &WindowContext) -> bool {
+    //         self.visible_panel()
+    //             .map_or(false, |panel| panel.has_focus(cx))
+    //     }
 
-    pub fn panel<T: Panel>(&self) -> Option<ViewHandle<T>> {
+    pub fn panel<T: Panel>(&self) -> Option<View<T>> {
         self.panel_entries
             .iter()
-            .find_map(|entry| entry.panel.as_any().clone().downcast())
+            .find_map(|entry| entry.panel.to_any().clone().downcast().ok())
     }
 
     pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
         self.panel_entries
             .iter()
-            .position(|entry| entry.panel.as_any().is::<T>())
+            .position(|entry| entry.panel.to_any().downcast::<T>().is_ok())
     }
 
-    pub fn panel_index_for_ui_name(&self, ui_name: &str, cx: &AppContext) -> Option<usize> {
-        self.panel_entries.iter().position(|entry| {
-            let panel = entry.panel.as_any();
-            cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name)
-        })
+    pub fn panel_index_for_persistent_name(
+        &self,
+        ui_name: &str,
+        _cx: &AppContext,
+    ) -> Option<usize> {
+        self.panel_entries
+            .iter()
+            .position(|entry| entry.panel.persistent_name() == ui_name)
     }
 
     pub fn active_panel_index(&self) -> usize {
@@ -234,14 +287,9 @@ impl Dock {
         }
     }
 
-    pub fn set_panel_zoomed(
-        &mut self,
-        panel: &AnyViewHandle,
-        zoomed: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
+    pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
         for entry in &mut self.panel_entries {
-            if entry.panel.as_any() == panel {
+            if entry.panel.panel_id() == panel.entity_id() {
                 if zoomed != entry.panel.is_zoomed(cx) {
                     entry.panel.set_zoomed(zoomed, cx);
                 }
@@ -261,46 +309,115 @@ impl Dock {
         }
     }
 
-    pub(crate) fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>) {
+    pub(crate) fn add_panel<T: Panel>(
+        &mut self,
+        panel: View<T>,
+        workspace: WeakView<Workspace>,
+        cx: &mut ViewContext<Self>,
+    ) {
         let subscriptions = [
             cx.observe(&panel, |_, _, cx| cx.notify()),
-            cx.subscribe(&panel, |this, panel, event, cx| {
-                if T::should_activate_on_event(event) {
+            cx.subscribe(&panel, move |this, panel, event, cx| match event {
+                PanelEvent::ChangePosition => {
+                    let new_position = panel.read(cx).position(cx);
+
+                    let Ok(new_dock) = workspace.update(cx, |workspace, cx| {
+                        if panel.is_zoomed(cx) {
+                            workspace.zoomed_position = Some(new_position);
+                        }
+                        match new_position {
+                            DockPosition::Left => &workspace.left_dock,
+                            DockPosition::Bottom => &workspace.bottom_dock,
+                            DockPosition::Right => &workspace.right_dock,
+                        }
+                        .clone()
+                    }) else {
+                        return;
+                    };
+
+                    let was_visible = this.is_open()
+                        && this.visible_panel().map_or(false, |active_panel| {
+                            active_panel.panel_id() == Entity::entity_id(&panel)
+                        });
+
+                    this.remove_panel(&panel, cx);
+
+                    new_dock.update(cx, |new_dock, cx| {
+                        new_dock.add_panel(panel.clone(), workspace.clone(), cx);
+                        if was_visible {
+                            new_dock.set_open(true, cx);
+                            new_dock.activate_panel(new_dock.panels_len() - 1, cx);
+                        }
+                    });
+                }
+                PanelEvent::ZoomIn => {
+                    this.set_panel_zoomed(&panel.to_any(), true, cx);
+                    if !panel.focus_handle(cx).contains_focused(cx) {
+                        cx.focus_view(&panel);
+                    }
+                    workspace
+                        .update(cx, |workspace, cx| {
+                            workspace.zoomed = Some(panel.downgrade().into());
+                            workspace.zoomed_position = Some(panel.read(cx).position(cx));
+                        })
+                        .ok();
+                }
+                PanelEvent::ZoomOut => {
+                    this.set_panel_zoomed(&panel.to_any(), false, cx);
+                    workspace
+                        .update(cx, |workspace, cx| {
+                            if workspace.zoomed_position == Some(this.position) {
+                                workspace.zoomed = None;
+                                workspace.zoomed_position = None;
+                            }
+                            cx.notify();
+                        })
+                        .ok();
+                }
+                // todo!() we do not use this event in the production code (even in zed1), remove it
+                PanelEvent::Activate => {
                     if let Some(ix) = this
                         .panel_entries
                         .iter()
-                        .position(|entry| entry.panel.id() == panel.id())
+                        .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel))
                     {
                         this.set_open(true, cx);
                         this.activate_panel(ix, cx);
-                        cx.focus(&panel);
+                        cx.focus_view(&panel);
+                    }
+                }
+                PanelEvent::Close => {
+                    if this
+                        .visible_panel()
+                        .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel))
+                    {
+                        this.set_open(false, cx);
                     }
-                } else if T::should_close_on_event(event)
-                    && this.visible_panel().map_or(false, |p| p.id() == panel.id())
-                {
-                    this.set_open(false, cx);
                 }
+                PanelEvent::Focus => {}
             }),
         ];
 
-        let dock_view_id = cx.view_id();
+        // todo!()
+        // let dock_view_id = cx.view_id();
         self.panel_entries.push(PanelEntry {
-            panel: Rc::new(panel),
-            context_menu: cx.add_view(|cx| {
-                let mut menu = ContextMenu::new(dock_view_id, cx);
-                menu.set_position_mode(OverlayPositionMode::Local);
-                menu
-            }),
+            panel: Arc::new(panel),
+            // todo!()
+            // context_menu: cx.add_view(|cx| {
+            //     let mut menu = ContextMenu::new(dock_view_id, cx);
+            //     menu.set_position_mode(OverlayPositionMode::Local);
+            //     menu
+            // }),
             _subscriptions: subscriptions,
         });
         cx.notify()
     }
 
-    pub fn remove_panel<T: Panel>(&mut self, panel: &ViewHandle<T>, cx: &mut ViewContext<Self>) {
+    pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
         if let Some(panel_ix) = self
             .panel_entries
             .iter()
-            .position(|entry| entry.panel.id() == panel.id())
+            .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel))
         {
             if panel_ix == self.active_panel_index {
                 self.active_panel_index = 0;
@@ -332,12 +449,12 @@ impl Dock {
         }
     }
 
-    pub fn visible_panel(&self) -> Option<&Rc<dyn PanelHandle>> {
+    pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
         let entry = self.visible_entry()?;
         Some(&entry.panel)
     }
 
-    pub fn active_panel(&self) -> Option<&Rc<dyn PanelHandle>> {
+    pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
         Some(&self.panel_entries.get(self.active_panel_index)?.panel)
     }
 
@@ -349,7 +466,7 @@ impl Dock {
         }
     }
 
-    pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Rc<dyn PanelHandle>> {
+    pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
         let entry = self.visible_entry()?;
         if entry.panel.is_zoomed(cx) {
             Some(entry.panel.clone())
@@ -358,14 +475,14 @@ impl Dock {
         }
     }
 
-    pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<f32> {
+    pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<Pixels> {
         self.panel_entries
             .iter()
-            .find(|entry| entry.panel.id() == panel.id())
+            .find(|entry| entry.panel.panel_id() == panel.panel_id())
             .map(|entry| entry.panel.size(cx))
     }
 
-    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<f32> {
+    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
         if self.is_open {
             self.panel_entries
                 .get(self.active_panel_index)
@@ -375,304 +492,243 @@ impl Dock {
         }
     }
 
-    pub fn resize_active_panel(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+    pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
         if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
+            let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
             entry.panel.set_size(size, cx);
             cx.notify();
         }
     }
 
-    pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement<Workspace> {
-        if let Some(active_entry) = self.visible_entry() {
-            Empty::new()
-                .into_any()
-                .contained()
-                .with_style(self.style(cx))
-                .resizable::<WorkspaceBounds>(
-                    self.position.to_resize_handle_side(),
-                    active_entry.panel.size(cx),
-                    |_, _, _| {},
-                )
-                .into_any()
-        } else {
-            Empty::new().into_any()
+    pub fn toggle_action(&self) -> Box<dyn Action> {
+        match self.position {
+            DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
+            DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
+            DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
         }
     }
-
-    fn style(&self, cx: &WindowContext) -> ContainerStyle {
-        let theme = &settings::get::<ThemeSettings>(cx).theme;
-        let style = match self.position {
-            DockPosition::Left => theme.workspace.dock.left,
-            DockPosition::Bottom => theme.workspace.dock.bottom,
-            DockPosition::Right => theme.workspace.dock.right,
-        };
-        style
-    }
 }
 
-impl Entity for Dock {
-    type Event = ();
-}
+impl Render for Dock {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        if let Some(entry) = self.visible_entry() {
+            let size = entry.panel.size(cx);
+
+            let position = self.position;
+            let mut handle = div()
+                .id("resize-handle")
+                .on_drag(DraggedDock(position), |dock, cx| {
+                    cx.stop_propagation();
+                    cx.new_view(|_| dock.clone())
+                })
+                .on_click(cx.listener(|v, e: &ClickEvent, cx| {
+                    if e.down.button == MouseButton::Left && e.down.click_count == 2 {
+                        v.resize_active_panel(None, cx);
+                        cx.stop_propagation();
+                    }
+                }))
+                .z_index(1)
+                .block_mouse();
+
+            match self.position() {
+                DockPosition::Left => {
+                    handle = handle
+                        .absolute()
+                        .right(px(0.))
+                        .top(px(0.))
+                        .h_full()
+                        .w(RESIZE_HANDLE_SIZE)
+                        .cursor_col_resize();
+                }
+                DockPosition::Bottom => {
+                    handle = handle
+                        .absolute()
+                        .top(px(0.))
+                        .left(px(0.))
+                        .w_full()
+                        .h(RESIZE_HANDLE_SIZE)
+                        .cursor_row_resize();
+                }
+                DockPosition::Right => {
+                    handle = handle
+                        .absolute()
+                        .top(px(0.))
+                        .left(px(0.))
+                        .h_full()
+                        .w(RESIZE_HANDLE_SIZE)
+                        .cursor_col_resize();
+                }
+            }
 
-impl View for Dock {
-    fn ui_name() -> &'static str {
-        "Dock"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        if let Some(active_entry) = self.visible_entry() {
-            let style = self.style(cx);
-            ChildView::new(active_entry.panel.as_any(), cx)
-                .contained()
-                .with_style(style)
-                .resizable::<WorkspaceBounds>(
-                    self.position.to_resize_handle_side(),
-                    active_entry.panel.size(cx),
-                    |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx),
+            div()
+                .flex()
+                .bg(cx.theme().colors().panel_background)
+                .border_color(cx.theme().colors().border)
+                .overflow_hidden()
+                .map(|this| match self.position().axis() {
+                    Axis::Horizontal => this.w(size).h_full().flex_row(),
+                    Axis::Vertical => this.h(size).w_full().flex_col(),
+                })
+                .map(|this| match self.position() {
+                    DockPosition::Left => this.border_r(),
+                    DockPosition::Right => this.border_l(),
+                    DockPosition::Bottom => this.border_t(),
+                })
+                .child(
+                    div()
+                        .map(|this| match self.position().axis() {
+                            Axis::Horizontal => this.min_w(size).h_full(),
+                            Axis::Vertical => this.min_h(size).w_full(),
+                        })
+                        .child(entry.panel.to_any()),
                 )
-                .into_any()
+                .child(handle)
         } else {
-            Empty::new().into_any()
-        }
-    }
-
-    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-        if cx.is_self_focused() {
-            if let Some(active_entry) = self.visible_entry() {
-                cx.focus(active_entry.panel.as_any());
-            } else {
-                cx.focus_parent();
-            }
+            div()
         }
     }
 }
 
 impl PanelButtons {
-    pub fn new(
-        dock: ViewHandle<Dock>,
-        workspace: WeakViewHandle<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
+    pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
         cx.observe(&dock, |_, _, cx| cx.notify()).detach();
-        Self { dock, workspace }
+        Self { dock }
     }
 }
 
-impl Entity for PanelButtons {
-    type Event = ();
-}
-
-impl View for PanelButtons {
-    fn ui_name() -> &'static str {
-        "PanelButtons"
-    }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &settings::get::<ThemeSettings>(cx).theme;
-        let tooltip_style = theme.tooltip.clone();
-        let theme = &theme.workspace.status_bar.panel_buttons;
-        let button_style = theme.button.clone();
+impl Render for PanelButtons {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        // todo!()
         let dock = self.dock.read(cx);
-        let active_ix = dock.active_panel_index;
+        let active_index = dock.active_panel_index;
         let is_open = dock.is_open;
         let dock_position = dock.position;
-        let group_style = match dock_position {
-            DockPosition::Left => theme.group_left,
-            DockPosition::Bottom => theme.group_bottom,
-            DockPosition::Right => theme.group_right,
-        };
-        let menu_corner = match dock_position {
-            DockPosition::Left => AnchorCorner::BottomLeft,
-            DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
+
+        let (menu_anchor, menu_attach) = match dock.position {
+            DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
+            DockPosition::Bottom | DockPosition::Right => {
+                (AnchorCorner::BottomRight, AnchorCorner::TopRight)
+            }
         };
 
-        let panels = dock
+        let buttons = dock
             .panel_entries
             .iter()
-            .map(|item| (item.panel.clone(), item.context_menu.clone()))
-            .collect::<Vec<_>>();
-        Flex::row()
-            .with_children(panels.into_iter().enumerate().filter_map(
-                |(panel_ix, (view, context_menu))| {
-                    let icon_path = view.icon_path(cx)?;
-                    let is_active = is_open && panel_ix == active_ix;
-                    let (tooltip, tooltip_action) = if is_active {
-                        (
-                            format!("Close {} dock", dock_position.to_label()),
-                            Some(match dock_position {
-                                DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
-                                DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
-                                DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
-                            }),
-                        )
-                    } else {
-                        view.icon_tooltip(cx)
-                    };
-                    Some(
-                        Stack::new()
-                            .with_child(
-                                MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
-                                    let style = button_style.in_state(is_active);
-
-                                    let style = style.style_for(state);
-                                    Flex::row()
-                                        .with_child(
-                                            Svg::new(icon_path)
-                                                .with_color(style.icon_color)
-                                                .constrained()
-                                                .with_width(style.icon_size)
-                                                .aligned(),
-                                        )
-                                        .with_children(if let Some(label) = view.icon_label(cx) {
-                                            Some(
-                                                Label::new(label, style.label.text.clone())
-                                                    .contained()
-                                                    .with_style(style.label.container)
-                                                    .aligned(),
-                                            )
-                                        } else {
-                                            None
+            .enumerate()
+            .filter_map(|(i, entry)| {
+                let icon = entry.panel.icon(cx)?;
+                let icon_tooltip = entry.panel.icon_tooltip(cx)?;
+                let name = entry.panel.persistent_name();
+                let panel = entry.panel.clone();
+
+                let is_active_button = i == active_index && is_open;
+
+                let (action, tooltip) = if is_active_button {
+                    let action = dock.toggle_action();
+
+                    let tooltip: SharedString =
+                        format!("Close {} dock", dock.position.to_label()).into();
+
+                    (action, tooltip)
+                } else {
+                    let action = entry.panel.toggle_action(cx);
+
+                    (action, icon_tooltip.into())
+                };
+
+                Some(
+                    right_click_menu(name)
+                        .menu(move |cx| {
+                            const POSITIONS: [DockPosition; 3] = [
+                                DockPosition::Left,
+                                DockPosition::Right,
+                                DockPosition::Bottom,
+                            ];
+
+                            ContextMenu::build(cx, |mut menu, cx| {
+                                for position in POSITIONS {
+                                    if position != dock_position
+                                        && panel.position_is_valid(position, cx)
+                                    {
+                                        let panel = panel.clone();
+                                        menu = menu.entry(position.to_label(), None, move |cx| {
+                                            panel.set_position(position, cx);
                                         })
-                                        .constrained()
-                                        .with_height(style.icon_size)
-                                        .contained()
-                                        .with_style(style.container)
-                                })
-                                .with_cursor_style(CursorStyle::PointingHand)
-                                .on_click(MouseButton::Left, {
-                                    let tooltip_action =
-                                        tooltip_action.as_ref().map(|action| action.boxed_clone());
-                                    move |_, this, cx| {
-                                        if let Some(tooltip_action) = &tooltip_action {
-                                            let window = cx.window();
-                                            let view_id = this.workspace.id();
-                                            let tooltip_action = tooltip_action.boxed_clone();
-                                            cx.spawn(|_, mut cx| async move {
-                                                window.dispatch_action(
-                                                    view_id,
-                                                    &*tooltip_action,
-                                                    &mut cx,
-                                                );
-                                            })
-                                            .detach();
-                                        }
                                     }
+                                }
+                                menu
+                            })
+                        })
+                        .anchor(menu_anchor)
+                        .attach(menu_attach)
+                        .trigger(
+                            IconButton::new(name, icon)
+                                .icon_size(IconSize::Small)
+                                .selected(is_active_button)
+                                .on_click({
+                                    let action = action.boxed_clone();
+                                    move |_, cx| cx.dispatch_action(action.boxed_clone())
                                 })
-                                .on_click(MouseButton::Right, {
-                                    let view = view.clone();
-                                    let menu = context_menu.clone();
-                                    move |_, _, cx| {
-                                        const POSITIONS: [DockPosition; 3] = [
-                                            DockPosition::Left,
-                                            DockPosition::Right,
-                                            DockPosition::Bottom,
-                                        ];
-
-                                        menu.update(cx, |menu, cx| {
-                                            let items = POSITIONS
-                                                .into_iter()
-                                                .filter(|position| {
-                                                    *position != dock_position
-                                                        && view.position_is_valid(*position, cx)
-                                                })
-                                                .map(|position| {
-                                                    let view = view.clone();
-                                                    ContextMenuItem::handler(
-                                                        format!("Dock {}", position.to_label()),
-                                                        move |cx| view.set_position(position, cx),
-                                                    )
-                                                })
-                                                .collect();
-                                            menu.show(Default::default(), menu_corner, items, cx);
-                                        })
-                                    }
-                                })
-                                .with_tooltip::<Self>(
-                                    panel_ix,
-                                    tooltip,
-                                    tooltip_action,
-                                    tooltip_style.clone(),
-                                    cx,
-                                ),
-                            )
-                            .with_child(ChildView::new(&context_menu, cx)),
-                    )
-                },
-            ))
-            .contained()
-            .with_style(group_style)
-            .into_any()
+                                .tooltip(move |cx| {
+                                    Tooltip::for_action(tooltip.clone(), &*action, cx)
+                                }),
+                        ),
+                )
+            });
+
+        h_stack().gap_0p5().children(buttons)
     }
 }
 
 impl StatusItemView for PanelButtons {
     fn set_active_pane_item(
         &mut self,
-        _: Option<&dyn crate::ItemHandle>,
-        _: &mut ViewContext<Self>,
+        _active_pane_item: Option<&dyn crate::ItemHandle>,
+        _cx: &mut ViewContext<Self>,
     ) {
+        // Nothing to do, panel buttons don't depend on the active center item
     }
 }
 
 #[cfg(any(test, feature = "test-support"))]
 pub mod test {
     use super::*;
-    use gpui::{ViewContext, WindowContext};
-
-    #[derive(Debug)]
-    pub enum TestPanelEvent {
-        PositionChanged,
-        Activated,
-        Closed,
-        ZoomIn,
-        ZoomOut,
-        Focus,
-    }
+    use gpui::{actions, div, ViewContext, WindowContext};
 
     pub struct TestPanel {
         pub position: DockPosition,
         pub zoomed: bool,
         pub active: bool,
-        pub has_focus: bool,
-        pub size: f32,
+        pub focus_handle: FocusHandle,
+        pub size: Pixels,
     }
+    actions!(test, [ToggleTestPanel]);
+
+    impl EventEmitter<PanelEvent> for TestPanel {}
 
     impl TestPanel {
-        pub fn new(position: DockPosition) -> Self {
+        pub fn new(position: DockPosition, cx: &mut WindowContext) -> Self {
             Self {
                 position,
                 zoomed: false,
                 active: false,
-                has_focus: false,
-                size: 300.,
+                focus_handle: cx.focus_handle(),
+                size: px(300.),
             }
         }
     }
 
-    impl Entity for TestPanel {
-        type Event = TestPanelEvent;
+    impl Render for TestPanel {
+        fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+            div()
+        }
     }
 
-    impl View for TestPanel {
-        fn ui_name() -> &'static str {
+    impl Panel for TestPanel {
+        fn persistent_name() -> &'static str {
             "TestPanel"
         }
 
-        fn render(&mut self, _: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
-            Empty::new().into_any()
-        }
-
-        fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
-            self.has_focus = true;
-            cx.emit(TestPanelEvent::Focus);
-        }
-
-        fn focus_out(&mut self, _: AnyViewHandle, _: &mut ViewContext<Self>) {
-            self.has_focus = false;
-        }
-    }
-
-    impl Panel for TestPanel {
         fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
             self.position
         }
@@ -683,63 +739,45 @@ pub mod test {
 
         fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
             self.position = position;
-            cx.emit(TestPanelEvent::PositionChanged);
+            cx.emit(PanelEvent::ChangePosition);
         }
 
-        fn is_zoomed(&self, _: &WindowContext) -> bool {
-            self.zoomed
-        }
-
-        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
-            self.zoomed = zoomed;
-        }
-
-        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
-            self.active = active;
-        }
-
-        fn size(&self, _: &WindowContext) -> f32 {
+        fn size(&self, _: &WindowContext) -> Pixels {
             self.size
         }
 
-        fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
-            self.size = size.unwrap_or(300.);
-        }
-
-        fn icon_path(&self, _: &WindowContext) -> Option<&'static str> {
-            Some("icons/test_panel.svg")
-        }
-
-        fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
-            ("Test Panel".into(), None)
+        fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
+            self.size = size.unwrap_or(px(300.));
         }
 
-        fn should_change_position_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::PositionChanged)
+        fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
+            None
         }
 
-        fn should_zoom_in_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::ZoomIn)
+        fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
+            None
         }
 
-        fn should_zoom_out_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::ZoomOut)
+        fn toggle_action(&self) -> Box<dyn Action> {
+            ToggleTestPanel.boxed_clone()
         }
 
-        fn should_activate_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::Activated)
+        fn is_zoomed(&self, _: &WindowContext) -> bool {
+            self.zoomed
         }
 
-        fn should_close_on_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::Closed)
+        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
+            self.zoomed = zoomed;
         }
 
-        fn has_focus(&self, _cx: &WindowContext) -> bool {
-            self.has_focus
+        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
+            self.active = active;
         }
+    }
 
-        fn is_focus_event(event: &Self::Event) -> bool {
-            matches!(event, TestPanelEvent::Focus)
+    impl FocusableView for TestPanel {
+        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
+            self.focus_handle.clone()
         }
     }
 }

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

@@ -1,29 +1,29 @@
 use crate::{
-    pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders,
-    ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
+    pane::{self, Pane},
+    persistence::model::ItemId,
+    searchable::SearchableItemHandle,
+    workspace_settings::{AutosaveSetting, WorkspaceSettings},
+    DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
+    ViewId, Workspace, WorkspaceId,
 };
-use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
 use anyhow::Result;
 use client::{
     proto::{self, PeerId},
     Client,
 };
-use gpui::geometry::vector::Vector2F;
-use gpui::AnyWindowHandle;
 use gpui::{
-    fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
-    ViewContext, ViewHandle, WeakViewHandle, WindowContext,
+    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
+    HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
+    WindowContext,
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use schemars::JsonSchema;
-use serde_derive::{Deserialize, Serialize};
-use settings::Setting;
+use serde::{Deserialize, Serialize};
+use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
-    borrow::Cow,
     cell::RefCell,
-    fmt,
     ops::Range,
     path::PathBuf,
     rc::Rc,
@@ -64,7 +64,7 @@ pub struct ItemSettingsContent {
     close_position: Option<ClosePosition>,
 }
 
-impl Setting for ItemSettings {
+impl Settings for ItemSettings {
     const KEY: Option<&'static str> = Some("tabs");
 
     type FileContent = ItemSettingsContent;
@@ -72,13 +72,13 @@ impl Setting for ItemSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
-    ) -> anyhow::Result<Self> {
+        _: &mut AppContext,
+    ) -> Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }
 }
 
-#[derive(Eq, PartialEq, Hash, Debug)]
+#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
 pub enum ItemEvent {
     CloseItem,
     UpdateTab,
@@ -92,30 +92,38 @@ pub struct BreadcrumbText {
     pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
 }
 
-pub trait Item: View {
+pub trait Item: FocusableView + EventEmitter<Self::Event> {
+    type Event;
+
     fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
     fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
         false
     }
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
         None
     }
-    fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<str>> {
+    fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
         None
     }
-    fn tab_content<V: 'static>(
+    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement;
+
+    /// (model id, Item)
+    fn for_each_project_item(
         &self,
-        detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<V>;
-    fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {} // (model id, Item)
+        _: &AppContext,
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
+    ) {
+    }
     fn is_singleton(&self, _cx: &AppContext) -> bool {
         false
     }
     fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
-    fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
+    fn clone_on_split(
+        &self,
+        _workspace_id: WorkspaceId,
+        _: &mut ViewContext<Self>,
+    ) -> Option<View<Self>>
     where
         Self: Sized,
     {
@@ -130,16 +138,12 @@ pub trait Item: View {
     fn can_save(&self, _cx: &AppContext) -> bool {
         false
     }
-    fn save(
-        &mut self,
-        _project: ModelHandle<Project>,
-        _cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
+    fn save(&mut self, _project: Model<Project>, _cx: &mut ViewContext<Self>) -> Task<Result<()>> {
         unimplemented!("save() must be implemented if can_save() returns true")
     }
     fn save_as(
         &mut self,
-        _project: ModelHandle<Project>,
+        _project: Model<Project>,
         _abs_path: PathBuf,
         _cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
@@ -147,35 +151,28 @@ pub trait Item: View {
     }
     fn reload(
         &mut self,
-        _project: ModelHandle<Project>,
+        _project: Model<Project>,
         _cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
         unimplemented!("reload() must be implemented if can_save() returns true")
     }
-    fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
-        SmallVec::new()
-    }
-    fn should_close_item_on_event(_: &Self::Event) -> bool {
-        false
-    }
-    fn should_update_tab_on_event(_: &Self::Event) -> bool {
-        false
-    }
+
+    fn to_item_events(event: &Self::Event, f: impl FnMut(ItemEvent));
 
     fn act_as_type<'a>(
         &'a self,
         type_id: TypeId,
-        self_handle: &'a ViewHandle<Self>,
+        self_handle: &'a View<Self>,
         _: &'a AppContext,
-    ) -> Option<&AnyViewHandle> {
+    ) -> Option<AnyView> {
         if TypeId::of::<Self>() == type_id {
-            Some(self_handle)
+            Some(self_handle.clone().into())
         } else {
             None
         }
     }
 
-    fn as_searchable(&self, _: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+    fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
         None
     }
 
@@ -194,12 +191,12 @@ pub trait Item: View {
     }
 
     fn deserialize(
-        _project: ModelHandle<Project>,
-        _workspace: WeakViewHandle<Workspace>,
+        _project: Model<Project>,
+        _workspace: WeakView<Workspace>,
         _workspace_id: WorkspaceId,
         _item_id: ItemId,
         _cx: &mut ViewContext<Pane>,
-    ) -> Task<Result<ViewHandle<Self>>> {
+    ) -> Task<Result<View<Self>>> {
         unimplemented!(
             "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
         )
@@ -207,35 +204,30 @@ pub trait Item: View {
     fn show_toolbar(&self) -> bool {
         true
     }
-    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
+    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
         None
     }
 }
 
-pub trait ItemHandle: 'static + fmt::Debug {
+pub trait ItemHandle: 'static + Send {
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
         handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
     ) -> gpui::Subscription;
-    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>>;
-    fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
-    fn tab_content(
-        &self,
-        detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<Pane>;
-    fn dragged_tab_content(
-        &self,
-        detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<Workspace>;
+    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
+    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
+    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement;
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
     fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
-    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]>;
-    fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item));
+    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
+    fn for_each_project_item(
+        &self,
+        _: &AppContext,
+        _: &mut dyn FnMut(EntityId, &dyn project::Item),
+    );
     fn is_singleton(&self, cx: &AppContext) -> bool;
     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
     fn clone_on_split(
@@ -246,95 +238,85 @@ pub trait ItemHandle: 'static + fmt::Debug {
     fn added_to_pane(
         &self,
         workspace: &mut Workspace,
-        pane: ViewHandle<Pane>,
+        pane: View<Pane>,
         cx: &mut ViewContext<Workspace>,
     );
     fn deactivated(&self, cx: &mut WindowContext);
     fn workspace_deactivated(&self, cx: &mut WindowContext);
     fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
-    fn id(&self) -> usize;
-    fn window(&self) -> AnyWindowHandle;
-    fn as_any(&self) -> &AnyViewHandle;
+    fn item_id(&self) -> EntityId;
+    fn to_any(&self) -> AnyView;
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
     fn can_save(&self, cx: &AppContext) -> bool;
-    fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
     fn save_as(
         &self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         abs_path: PathBuf,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
-    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>;
+    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
     fn on_release(
         &self,
         cx: &mut AppContext,
-        callback: Box<dyn FnOnce(&mut AppContext)>,
+        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
     ) -> gpui::Subscription;
     fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
     fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
     fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
     fn serialized_item_kind(&self) -> Option<&'static str>;
     fn show_toolbar(&self, cx: &AppContext) -> bool;
-    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F>;
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
 }
 
-pub trait WeakItemHandle {
-    fn id(&self) -> usize;
-    fn window(&self) -> AnyWindowHandle;
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+pub trait WeakItemHandle: Send + Sync {
+    fn id(&self) -> EntityId;
+    fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
 }
 
 impl dyn ItemHandle {
-    pub fn downcast<T: View>(&self) -> Option<ViewHandle<T>> {
-        self.as_any().clone().downcast()
+    pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
+        self.to_any().downcast().ok()
     }
 
-    pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
-        self.act_as_type(TypeId::of::<T>(), cx)
-            .and_then(|t| t.clone().downcast())
+    pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
+        self.act_as_type(TypeId::of::<V>(), cx)
+            .and_then(|t| t.downcast().ok())
     }
 }
 
-impl<T: Item> ItemHandle for ViewHandle<T> {
+impl<T: Item> ItemHandle for View<T> {
     fn subscribe_to_item_events(
         &self,
         cx: &mut WindowContext,
         handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
     ) -> gpui::Subscription {
         cx.subscribe(self, move |_, event, cx| {
-            for item_event in T::to_item_events(event) {
-                handler(item_event, cx)
-            }
+            T::to_item_events(event, |item_event| handler(item_event, cx));
         })
     }
 
-    fn tab_tooltip_text<'a>(&self, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
+        self.focus_handle(cx)
+    }
+
+    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
         self.read(cx).tab_tooltip_text(cx)
     }
 
-    fn tab_description<'a>(&'a self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
+    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString> {
         self.read(cx).tab_description(detail, cx)
     }
 
-    fn tab_content(
-        &self,
-        detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<Pane> {
-        self.read(cx).tab_content(detail, style, cx)
+    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
+        self.read(cx).tab_content(detail, selected, cx)
     }
 
-    fn dragged_tab_content(
-        &self,
-        detail: Option<usize>,
-        style: &theme::Tab,
-        cx: &AppContext,
-    ) -> AnyElement<Workspace> {
-        self.read(cx).tab_content(detail, style, cx)
+    fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
+        self.read(cx).tab_content(detail, true, cx)
     }
 
     fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
@@ -358,7 +340,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         result
     }
 
-    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[usize; 3]> {
+    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]> {
         let mut result = SmallVec::new();
         self.read(cx).for_each_project_item(cx, &mut |id, _| {
             result.push(id);
@@ -366,7 +348,11 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         result
     }
 
-    fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
+    fn for_each_project_item(
+        &self,
+        cx: &AppContext,
+        f: &mut dyn FnMut(EntityId, &dyn project::Item),
+    ) {
         self.read(cx).for_each_project_item(cx, f)
     }
 
@@ -383,18 +369,17 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         workspace_id: WorkspaceId,
         cx: &mut WindowContext,
     ) -> Option<Box<dyn ItemHandle>> {
-        self.update(cx, |item, cx| {
-            cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
-        })
-        .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
+        self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
+            .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
     }
 
     fn added_to_pane(
         &self,
         workspace: &mut Workspace,
-        pane: ViewHandle<Pane>,
+        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);
@@ -419,19 +404,19 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
 
         if workspace
             .panes_by_item
-            .insert(self.id(), pane.downgrade())
+            .insert(self.item_id(), pane.downgrade())
             .is_none()
         {
             let mut pending_autosave = DelayedDebouncedEditAction::new();
             let pending_update = Rc::new(RefCell::new(None));
-            let pending_update_scheduled = Rc::new(AtomicBool::new(false));
+            let pending_update_scheduled = Arc::new(AtomicBool::new(false));
 
             let mut event_subscription =
                 Some(cx.subscribe(self, move |workspace, item, event, cx| {
                     let pane = if let Some(pane) = workspace
                         .panes_by_item
-                        .get(&item.id())
-                        .and_then(|pane| pane.upgrade(cx))
+                        .get(&item.item_id())
+                        .and_then(|pane| pane.upgrade())
                     {
                         pane
                     } else {
@@ -443,7 +428,10 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                         let is_project_item = item.is_project_item(cx);
                         let leader_id = workspace.leader_for_pane(&pane);
 
-                        if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
+                        let follow_event = item.to_follow_event(event);
+                        if leader_id.is_some()
+                            && matches!(follow_event, Some(FollowEvent::Unfollow))
+                        {
                             workspace.unfollow(&pane, cx);
                         }
 
@@ -454,7 +442,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                         ) && !pending_update_scheduled.load(Ordering::SeqCst)
                         {
                             pending_update_scheduled.store(true, Ordering::SeqCst);
-                            cx.after_window_update({
+                            cx.on_next_frame({
                                 let pending_update = pending_update.clone();
                                 let pending_update_scheduled = pending_update_scheduled.clone();
                                 move |this, cx| {
@@ -477,51 +465,48 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                         }
                     }
 
-                    for item_event in T::to_item_events(event).into_iter() {
-                        match item_event {
-                            ItemEvent::CloseItem => {
-                                pane.update(cx, |pane, cx| {
-                                    pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
-                                })
-                                .detach_and_log_err(cx);
-                                return;
-                            }
+                    T::to_item_events(event, |event| match event {
+                        ItemEvent::CloseItem => {
+                            pane.update(cx, |pane, cx| {
+                                pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
+                            })
+                            .detach_and_log_err(cx);
+                            return;
+                        }
 
-                            ItemEvent::UpdateTab => {
-                                pane.update(cx, |_, cx| {
-                                    cx.emit(pane::Event::ChangeItemTitle);
-                                    cx.notify();
-                                });
-                            }
+                        ItemEvent::UpdateTab => {
+                            pane.update(cx, |_, cx| {
+                                cx.emit(pane::Event::ChangeItemTitle);
+                                cx.notify();
+                            });
+                        }
 
-                            ItemEvent::Edit => {
-                                let autosave = settings::get::<WorkspaceSettings>(cx).autosave;
-                                if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
-                                    let delay = Duration::from_millis(milliseconds);
-                                    let item = item.clone();
-                                    pending_autosave.fire_new(delay, cx, move |workspace, cx| {
-                                        Pane::autosave_item(&item, workspace.project().clone(), cx)
-                                    });
-                                }
+                        ItemEvent::Edit => {
+                            let autosave = WorkspaceSettings::get_global(cx).autosave;
+                            if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
+                                let delay = Duration::from_millis(milliseconds);
+                                let item = item.clone();
+                                pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+                                    Pane::autosave_item(&item, workspace.project().clone(), cx)
+                                });
                             }
-
-                            _ => {}
                         }
-                    }
+
+                        _ => {}
+                    });
                 }));
 
-            cx.observe_focus(self, move |workspace, item, focused, cx| {
-                if !focused
-                    && settings::get::<WorkspaceSettings>(cx).autosave
-                        == AutosaveSetting::OnFocusChange
-                {
-                    Pane::autosave_item(&item, workspace.project.clone(), cx)
-                        .detach_and_log_err(cx);
+            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.id();
+            let item_id = self.item_id();
             cx.observe_release(self, move |workspace, _, _| {
                 workspace.panes_by_item.remove(&item_id);
                 event_subscription.take();
@@ -546,16 +531,12 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         self.update(cx, |this, cx| this.navigate(data, cx))
     }
 
-    fn id(&self) -> usize {
-        self.id()
+    fn item_id(&self) -> EntityId {
+        self.entity_id()
     }
 
-    fn window(&self) -> AnyWindowHandle {
-        AnyViewHandle::window(self)
-    }
-
-    fn as_any(&self) -> &AnyViewHandle {
-        self
+    fn to_any(&self) -> AnyView {
+        self.clone().into()
     }
 
     fn is_dirty(&self, cx: &AppContext) -> bool {
@@ -570,32 +551,32 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         self.read(cx).can_save(cx)
     }
 
-    fn save(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.save(project, cx))
     }
 
     fn save_as(
         &self,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         abs_path: PathBuf,
         cx: &mut WindowContext,
     ) -> Task<anyhow::Result<()>> {
         self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
     }
 
-    fn reload(&self, project: ModelHandle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.reload(project, cx))
     }
 
-    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
+    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
         self.read(cx).act_as_type(type_id, self, cx)
     }
 
     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
         if cx.has_global::<FollowableItemBuilders>() {
             let builders = cx.global::<FollowableItemBuilders>();
-            let item = self.as_any();
-            Some(builders.get(&item.view_type())?.1(item))
+            let item = self.to_any();
+            Some(builders.get(&item.entity_type())?.1(&item))
         } else {
             None
         }
@@ -604,7 +585,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
     fn on_release(
         &self,
         cx: &mut AppContext,
-        callback: Box<dyn FnOnce(&mut AppContext)>,
+        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
     ) -> gpui::Subscription {
         cx.observe_release(self, move |_, cx| callback(cx))
     }
@@ -629,20 +610,20 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         self.read(cx).show_toolbar()
     }
 
-    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
+    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
         self.read(cx).pixel_position_of_cursor(cx)
     }
 }
 
-impl From<Box<dyn ItemHandle>> for AnyViewHandle {
+impl From<Box<dyn ItemHandle>> for AnyView {
     fn from(val: Box<dyn ItemHandle>) -> Self {
-        val.as_any().clone()
+        val.to_any()
     }
 }
 
-impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
+impl From<&Box<dyn ItemHandle>> for AnyView {
     fn from(val: &Box<dyn ItemHandle>) -> Self {
-        val.as_any().clone()
+        val.to_any()
     }
 }
 
@@ -652,84 +633,85 @@ impl Clone for Box<dyn ItemHandle> {
     }
 }
 
-impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
-    fn id(&self) -> usize {
-        self.id()
-    }
-
-    fn window(&self) -> AnyWindowHandle {
-        self.window()
+impl<T: Item> WeakItemHandle for WeakView<T> {
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-        self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
+    fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
+        self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
     }
 }
 
 pub trait ProjectItem: Item {
-    type Item: project::Item + gpui::Entity;
+    type Item: project::Item;
 
     fn for_project_item(
-        project: ModelHandle<Project>,
-        item: ModelHandle<Self::Item>,
+        project: Model<Project>,
+        item: Model<Self::Item>,
         cx: &mut ViewContext<Self>,
-    ) -> Self;
+    ) -> Self
+    where
+        Self: Sized;
+}
+
+pub enum FollowEvent {
+    Unfollow,
 }
 
 pub trait FollowableItem: Item {
     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: ViewHandle<Pane>,
-        project: ViewHandle<Workspace>,
+        pane: View<Pane>,
+        project: View<Workspace>,
         id: ViewId,
         state: &mut Option<proto::view::Variant>,
-        cx: &mut AppContext,
-    ) -> Option<Task<Result<ViewHandle<Self>>>>;
+        cx: &mut WindowContext,
+    ) -> Option<Task<Result<View<Self>>>>;
+    fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
     fn add_event_to_update_proto(
         &self,
         event: &Self::Event,
         update: &mut Option<proto::update_view::Variant>,
-        cx: &AppContext,
+        cx: &WindowContext,
     ) -> bool;
     fn apply_update_proto(
         &mut self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         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>);
-    fn should_unfollow_on_event(event: &Self::Event, cx: &AppContext) -> bool;
 }
 
 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(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         message: proto::update_view::Variant,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool;
-    fn is_project_item(&self, cx: &AppContext) -> bool;
+    fn is_project_item(&self, cx: &WindowContext) -> bool;
 }
 
-impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
-    fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+impl<T: FollowableItem> FollowableItemHandle for View<T> {
+    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,
-                id: self.id() as u64,
+                id: self.item_id().as_u64(),
             })
         })
     }
@@ -738,7 +720,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<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)
     }
 
@@ -746,7 +728,7 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<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)
@@ -755,24 +737,20 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
         }
     }
 
+    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
+        T::to_follow_event(event.downcast_ref()?)
+    }
+
     fn apply_update_proto(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         message: proto::update_view::Variant,
         cx: &mut WindowContext,
     ) -> Task<Result<()>> {
         self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
     }
 
-    fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
-        if let Some(event) = event.downcast_ref() {
-            T::should_unfollow_on_event(event, cx)
-        } else {
-            false
-        }
-    }
-
-    fn is_project_item(&self, cx: &AppContext) -> bool {
+    fn is_project_item(&self, cx: &WindowContext) -> bool {
         self.read(cx).is_project_item(cx)
     }
 }
@@ -782,12 +760,12 @@ pub mod test {
     use super::{Item, ItemEvent};
     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
     use gpui::{
-        elements::Empty, AnyElement, AppContext, Element, Entity, ModelHandle, Task, View,
-        ViewContext, ViewHandle, WeakViewHandle,
+        AnyElement, AppContext, Context as _, EntityId, EventEmitter, FocusableView,
+        InteractiveElement, IntoElement, Model, Render, SharedString, Task, View, ViewContext,
+        VisualContext, WeakView,
     };
     use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
-    use smallvec::SmallVec;
-    use std::{any::Any, borrow::Cow, cell::Cell, path::Path};
+    use std::{any::Any, cell::Cell, path::Path};
 
     pub struct TestProjectItem {
         pub entry_id: Option<ProjectEntryId>,
@@ -804,14 +782,11 @@ pub mod test {
         pub is_dirty: bool,
         pub is_singleton: bool,
         pub has_conflict: bool,
-        pub project_items: Vec<ModelHandle<TestProjectItem>>,
+        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 = ();
+        focus_handle: gpui::FocusHandle,
     }
 
     impl project::Item for TestProjectItem {
@@ -828,41 +803,42 @@ pub mod test {
         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 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) -> ModelHandle<Self> {
+        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 {
+            cx.new_model(|_| Self {
                 entry_id,
                 project_path,
             })
         }
 
-        pub fn new_untitled(cx: &mut AppContext) -> ModelHandle<Self> {
-            cx.add_model(|_| Self {
+        pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
+            cx.new_model(|_| Self {
                 project_path: None,
                 entry_id: None,
             })
@@ -870,7 +846,7 @@ pub mod test {
     }
 
     impl TestItem {
-        pub fn new() -> Self {
+        pub fn new(cx: &mut ViewContext<Self>) -> Self {
             Self {
                 state: String::new(),
                 label: String::new(),
@@ -885,11 +861,12 @@ pub mod test {
                 tab_descriptions: None,
                 tab_detail: Default::default(),
                 workspace_id: 0,
+                focus_handle: cx.focus_handle(),
             }
         }
 
-        pub fn new_deserialized(id: WorkspaceId) -> Self {
-            let mut this = Self::new();
+        pub fn new_deserialized(id: WorkspaceId, cx: &mut ViewContext<Self>) -> Self {
+            let mut this = Self::new(cx);
             this.workspace_id = id;
             this
         }
@@ -914,7 +891,7 @@ pub mod test {
             self
         }
 
-        pub fn with_project_items(mut self, items: &[ModelHandle<TestProjectItem>]) -> Self {
+        pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
             self.project_items.clear();
             self.project_items.extend(items.iter().cloned());
             self
@@ -932,46 +909,52 @@ pub mod test {
         }
     }
 
-    impl Entity for TestItem {
-        type Event = TestItemEvent;
+    impl Render for TestItem {
+        fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
+            gpui::div().track_focus(&self.focus_handle)
+        }
     }
 
-    impl View for TestItem {
-        fn ui_name() -> &'static str {
-            "TestItem"
-        }
+    impl EventEmitter<ItemEvent> for TestItem {}
 
-        fn render(&mut self, _: &mut ViewContext<Self>) -> AnyElement<Self> {
-            Empty::new().into_any()
+    impl FocusableView for TestItem {
+        fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
+            self.focus_handle.clone()
         }
     }
 
     impl Item for TestItem {
-        fn tab_description(&self, detail: usize, _: &AppContext) -> Option<Cow<str>> {
+        type Event = ItemEvent;
+
+        fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
+            f(*event)
+        }
+
+        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<V: 'static>(
+        fn tab_content(
             &self,
             detail: Option<usize>,
-            _: &theme::Tab,
-            _: &AppContext,
-        ) -> AnyElement<V> {
+            _selected: bool,
+            _cx: &ui::prelude::WindowContext,
+        ) -> AnyElement {
             self.tab_detail.set(detail);
-            Empty::new().into_any()
+            gpui::div().into_any_element()
         }
 
         fn for_each_project_item(
             &self,
             cx: &AppContext,
-            f: &mut dyn FnMut(usize, &dyn project::Item),
+            f: &mut dyn FnMut(EntityId, &dyn project::Item),
         ) {
             self.project_items
                 .iter()
-                .for_each(|item| f(item.id(), item.read(cx)))
+                .for_each(|item| f(item.entity_id(), item.read(cx)))
         }
 
         fn is_singleton(&self, _: &AppContext) -> bool {

crates/workspace/src/notifications.rs πŸ”—

@@ -1,35 +1,39 @@
 use crate::{Toast, Workspace};
 use collections::HashMap;
-use gpui::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle};
+use gpui::{
+    AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
+    View, ViewContext, VisualContext,
+};
 use std::{any::TypeId, ops::DerefMut};
 
 pub fn init(cx: &mut AppContext) {
     cx.set_global(NotificationTracker::new());
-    simple_message_notification::init(cx);
+    // todo!()
+    // simple_message_notification::init(cx);
 }
 
-pub trait Notification: View {
-    fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool;
-}
+pub trait Notification: EventEmitter<DismissEvent> + Render {}
+
+impl<V: EventEmitter<DismissEvent> + Render> Notification for V {}
 
-pub trait NotificationHandle {
-    fn id(&self) -> usize;
-    fn as_any(&self) -> &AnyViewHandle;
+pub trait NotificationHandle: Send {
+    fn id(&self) -> EntityId;
+    fn to_any(&self) -> AnyView;
 }
 
-impl<T: Notification> NotificationHandle for ViewHandle<T> {
-    fn id(&self) -> usize {
-        self.id()
+impl<T: Notification> NotificationHandle for View<T> {
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
-    fn as_any(&self) -> &AnyViewHandle {
-        self
+    fn to_any(&self) -> AnyView {
+        self.clone().into()
     }
 }
 
-impl From<&dyn NotificationHandle> for AnyViewHandle {
+impl From<&dyn NotificationHandle> for AnyView {
     fn from(val: &dyn NotificationHandle) -> Self {
-        val.as_any().clone()
+        val.to_any()
     }
 }
 
@@ -75,14 +79,12 @@ impl Workspace {
         &mut self,
         id: usize,
         cx: &mut ViewContext<Self>,
-        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
+        build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
     ) {
         if !self.has_shown_notification_once::<V>(id, cx) {
-            cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
-                let entry = tracker.entry(TypeId::of::<V>()).or_default();
-                entry.push(id);
-            });
-
+            let tracker = cx.global_mut::<NotificationTracker>();
+            let entry = tracker.entry(TypeId::of::<V>()).or_default();
+            entry.push(id);
             self.show_notification::<V>(id, cx, build_notification)
         }
     }
@@ -91,7 +93,7 @@ impl Workspace {
         &mut self,
         id: usize,
         cx: &mut ViewContext<Self>,
-        build_notification: impl FnOnce(&mut ViewContext<Self>) -> ViewHandle<V>,
+        build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
     ) {
         let type_id = TypeId::of::<V>();
         if self
@@ -102,10 +104,8 @@ impl Workspace {
             })
         {
             let notification = build_notification(cx);
-            cx.subscribe(&notification, move |this, handle, event, cx| {
-                if handle.read(cx).should_dismiss_notification_on_event(event) {
-                    this.dismiss_notification_internal(type_id, id, cx);
-                }
+            cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
+                this.dismiss_notification_internal(type_id, id, cx);
             })
             .detach();
             self.notifications
@@ -114,6 +114,17 @@ impl Workspace {
         }
     }
 
+    pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
+    where
+        E: std::fmt::Debug,
+    {
+        self.show_notification(0, cx, |cx| {
+            cx.new_view(|_cx| {
+                simple_message_notification::MessageNotification::new(format!("Error: {err:?}"))
+            })
+        });
+    }
+
     pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
         let type_id = TypeId::of::<V>();
 
@@ -123,7 +134,7 @@ impl Workspace {
     pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
         self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
         self.show_notification(toast.id, cx, |cx| {
-            cx.add_view(|_cx| match toast.on_click.as_ref() {
+            cx.new_view(|_cx| match toast.on_click.as_ref() {
                 Some((click_msg, on_click)) => {
                     let on_click = on_click.clone();
                     simple_message_notification::MessageNotification::new(toast.msg.clone())
@@ -158,78 +169,29 @@ impl Workspace {
 }
 
 pub mod simple_message_notification {
-    use super::Notification;
-    use crate::Workspace;
     use gpui::{
-        actions,
-        elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
-        fonts::TextStyle,
-        impl_actions,
-        platform::{CursorStyle, MouseButton},
-        AnyElement, AppContext, Element, Entity, View, ViewContext,
+        div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
+        StatefulInteractiveElement, Styled, ViewContext,
     };
-    use menu::Cancel;
-    use serde::Deserialize;
-    use std::{borrow::Cow, sync::Arc};
-
-    actions!(message_notifications, [CancelMessageNotification]);
-
-    #[derive(Clone, Default, Deserialize, PartialEq)]
-    pub struct OsOpen(pub Cow<'static, str>);
-
-    impl OsOpen {
-        pub fn new<I: Into<Cow<'static, str>>>(url: I) -> Self {
-            OsOpen(url.into())
-        }
-    }
-
-    impl_actions!(message_notifications, [OsOpen]);
-
-    pub fn init(cx: &mut AppContext) {
-        cx.add_action(MessageNotification::dismiss);
-        cx.add_action(
-            |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
-                cx.platform().open_url(open_action.0.as_ref());
-            },
-        )
-    }
-
-    enum NotificationMessage {
-        Text(Cow<'static, str>),
-        Element(fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>),
-    }
+    use std::sync::Arc;
+    use ui::prelude::*;
+    use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
 
     pub struct MessageNotification {
-        message: NotificationMessage,
+        message: SharedString,
         on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
-        click_message: Option<Cow<'static, str>>,
+        click_message: Option<SharedString>,
     }
 
-    pub enum MessageNotificationEvent {
-        Dismiss,
-    }
-
-    impl Entity for MessageNotification {
-        type Event = MessageNotificationEvent;
-    }
+    impl EventEmitter<DismissEvent> for MessageNotification {}
 
     impl MessageNotification {
         pub fn new<S>(message: S) -> MessageNotification
         where
-            S: Into<Cow<'static, str>>,
+            S: Into<SharedString>,
         {
             Self {
-                message: NotificationMessage::Text(message.into()),
-                on_click: None,
-                click_message: None,
-            }
-        }
-
-        pub fn new_element(
-            message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
-        ) -> MessageNotification {
-            Self {
-                message: NotificationMessage::Element(message),
+                message: message.into(),
                 on_click: None,
                 click_message: None,
             }
@@ -237,7 +199,7 @@ pub mod simple_message_notification {
 
         pub fn with_click_message<S>(mut self, message: S) -> Self
         where
-            S: Into<Cow<'static, str>>,
+            S: Into<SharedString>,
         {
             self.click_message = Some(message.into());
             self
@@ -251,117 +213,139 @@ pub mod simple_message_notification {
             self
         }
 
-        pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
-            cx.emit(MessageNotificationEvent::Dismiss);
+        pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
+            cx.emit(DismissEvent);
         }
     }
 
-    impl View for MessageNotification {
-        fn ui_name() -> &'static str {
-            "MessageNotification"
-        }
-
-        fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-            let theme = theme::current(cx).clone();
-            let theme = &theme.simple_message_notification;
-
-            enum MessageNotificationTag {}
-
-            let click_message = self.click_message.clone();
-            let message = match &self.message {
-                NotificationMessage::Text(text) => {
-                    Text::new(text.to_owned(), theme.message.text.clone()).into_any()
-                }
-                NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
-            };
-            let on_click = self.on_click.clone();
-            let has_click_action = on_click.is_some();
-
-            Flex::column()
-                .with_child(
-                    Flex::row()
-                        .with_child(
-                            message
-                                .contained()
-                                .with_style(theme.message.container)
-                                .aligned()
-                                .top()
-                                .left()
-                                .flex(1., true),
-                        )
-                        .with_child(
-                            MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
-                                let style = theme.dismiss_button.style_for(state);
-                                Svg::new("icons/x.svg")
-                                    .with_color(style.color)
-                                    .constrained()
-                                    .with_width(style.icon_width)
-                                    .aligned()
-                                    .contained()
-                                    .with_style(style.container)
-                                    .constrained()
-                                    .with_width(style.button_width)
-                                    .with_height(style.button_width)
-                            })
-                            .with_padding(Padding::uniform(5.))
-                            .on_click(MouseButton::Left, move |_, this, cx| {
-                                this.dismiss(&Default::default(), cx);
-                            })
-                            .with_cursor_style(CursorStyle::PointingHand)
-                            .aligned()
-                            .constrained()
-                            .with_height(cx.font_cache().line_height(theme.message.text.font_size))
-                            .aligned()
-                            .top()
-                            .flex_float(),
+    impl Render for MessageNotification {
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+            v_stack()
+                .elevation_3(cx)
+                .p_4()
+                .child(
+                    h_stack()
+                        .justify_between()
+                        .child(div().max_w_80().child(Label::new(self.message.clone())))
+                        .child(
+                            div()
+                                .id("cancel")
+                                .child(IconElement::new(Icon::Close))
+                                .cursor_pointer()
+                                .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
                         ),
                 )
-                .with_children({
-                    click_message
-                        .map(|click_message| {
-                            MouseEventHandler::new::<MessageNotificationTag, _>(
-                                0,
-                                cx,
-                                |state, _| {
-                                    let style = theme.action_message.style_for(state);
-
-                                    Flex::row()
-                                        .with_child(
-                                            Text::new(click_message, style.text.clone())
-                                                .contained()
-                                                .with_style(style.container),
-                                        )
-                                        .contained()
-                                },
-                            )
-                            .on_click(MouseButton::Left, move |_, this, cx| {
-                                if let Some(on_click) = on_click.as_ref() {
-                                    on_click(cx);
-                                    this.dismiss(&Default::default(), cx);
-                                }
-                            })
-                            // Since we're not using a proper overlay, we have to capture these extra events
-                            .on_down(MouseButton::Left, |_, _, _| {})
-                            .on_up(MouseButton::Left, |_, _, _| {})
-                            .with_cursor_style(if has_click_action {
-                                CursorStyle::PointingHand
-                            } else {
-                                CursorStyle::Arrow
-                            })
-                        })
-                        .into_iter()
-                })
-                .into_any()
-        }
-    }
-
-    impl Notification for MessageNotification {
-        fn should_dismiss_notification_on_event(&self, event: &<Self as Entity>::Event) -> bool {
-            match event {
-                MessageNotificationEvent::Dismiss => true,
-            }
+                .children(self.click_message.iter().map(|message| {
+                    Button::new(message.clone(), message.clone()).on_click(cx.listener(
+                        |this, _, cx| {
+                            if let Some(on_click) = this.on_click.as_ref() {
+                                (on_click)(cx)
+                            };
+                            this.dismiss(cx)
+                        },
+                    ))
+                }))
         }
     }
+    // todo!()
+    //     impl View for MessageNotification {
+    //         fn ui_name() -> &'static str {
+    //             "MessageNotification"
+    //         }
+
+    //         fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
+    //             let theme = theme2::current(cx).clone();
+    //             let theme = &theme.simple_message_notification;
+
+    //             enum MessageNotificationTag {}
+
+    //             let click_message = self.click_message.clone();
+    //             let message = match &self.message {
+    //                 NotificationMessage::Text(text) => {
+    //                     Text::new(text.to_owned(), theme.message.text.clone()).into_any()
+    //                 }
+    //                 NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
+    //             };
+    //             let on_click = self.on_click.clone();
+    //             let has_click_action = on_click.is_some();
+
+    //             Flex::column()
+    //                 .with_child(
+    //                     Flex::row()
+    //                         .with_child(
+    //                             message
+    //                                 .contained()
+    //                                 .with_style(theme.message.container)
+    //                                 .aligned()
+    //                                 .top()
+    //                                 .left()
+    //                                 .flex(1., true),
+    //                         )
+    //                         .with_child(
+    //                             MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
+    //                                 let style = theme.dismiss_button.style_for(state);
+    //                                 Svg::new("icons/x.svg")
+    //                                     .with_color(style.color)
+    //                                     .constrained()
+    //                                     .with_width(style.icon_width)
+    //                                     .aligned()
+    //                                     .contained()
+    //                                     .with_style(style.container)
+    //                                     .constrained()
+    //                                     .with_width(style.button_width)
+    //                                     .with_height(style.button_width)
+    //                             })
+    //                             .with_padding(Padding::uniform(5.))
+    //                             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                                 this.dismiss(&Default::default(), cx);
+    //                             })
+    //                             .with_cursor_style(CursorStyle::PointingHand)
+    //                             .aligned()
+    //                             .constrained()
+    //                             .with_height(cx.font_cache().line_height(theme.message.text.font_size))
+    //                             .aligned()
+    //                             .top()
+    //                             .flex_float(),
+    //                         ),
+    //                 )
+    //                 .with_children({
+    //                     click_message
+    //                         .map(|click_message| {
+    //                             MouseEventHandler::new::<MessageNotificationTag, _>(
+    //                                 0,
+    //                                 cx,
+    //                                 |state, _| {
+    //                                     let style = theme.action_message.style_for(state);
+
+    //                                     Flex::row()
+    //                                         .with_child(
+    //                                             Text::new(click_message, style.text.clone())
+    //                                                 .contained()
+    //                                                 .with_style(style.container),
+    //                                         )
+    //                                         .contained()
+    //                                 },
+    //                             )
+    //                             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                                 if let Some(on_click) = on_click.as_ref() {
+    //                                     on_click(cx);
+    //                                     this.dismiss(&Default::default(), cx);
+    //                                 }
+    //                             })
+    //                             // Since we're not using a proper overlay, we have to capture these extra events
+    //                             .on_down(MouseButton::Left, |_, _, _| {})
+    //                             .on_up(MouseButton::Left, |_, _, _| {})
+    //                             .with_cursor_style(if has_click_action {
+    //                                 CursorStyle::PointingHand
+    //                             } else {
+    //                                 CursorStyle::Arrow
+    //                             })
+    //                         })
+    //                         .into_iter()
+    //                 })
+    //                 .into_any()
+    //         }
+    //     }
 }
 
 pub trait NotifyResultExt {
@@ -372,6 +356,8 @@ pub trait NotifyResultExt {
         workspace: &mut Workspace,
         cx: &mut ViewContext<Workspace>,
     ) -> Option<Self::Ok>;
+
+    fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
 }
 
 impl<T, E> NotifyResultExt for Result<T, E>
@@ -384,15 +370,24 @@ where
         match self {
             Ok(value) => Some(value),
             Err(err) => {
-                workspace.show_notification(0, cx, |cx| {
-                    cx.add_view(|_cx| {
-                        simple_message_notification::MessageNotification::new(format!(
-                            "Error: {:?}",
-                            err,
-                        ))
-                    })
-                });
+                log::error!("TODO {err:?}");
+                workspace.show_error(&err, cx);
+                None
+            }
+        }
+    }
 
+    fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
+        match self {
+            Ok(value) => Some(value),
+            Err(err) => {
+                log::error!("TODO {err:?}");
+                cx.update(|view, cx| {
+                    if let Ok(workspace) = view.downcast::<Workspace>() {
+                        workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
+                    }
+                })
+                .ok();
                 None
             }
         }

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

@@ -1,39 +1,24 @@
-mod dragged_item_receiver;
-
-use super::{ItemHandle, SplitDirection};
-pub use crate::toolbar::Toolbar;
 use crate::{
-    item::{ItemSettings, WeakItemHandle},
-    notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
-    Workspace, WorkspaceSettings,
+    item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
+    toolbar::Toolbar,
+    workspace_settings::{AutosaveSetting, WorkspaceSettings},
+    NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
 };
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
-use context_menu::{ContextMenu, ContextMenuItem};
-use drag_and_drop::{DragAndDrop, Draggable};
-use dragged_item_receiver::dragged_item_receiver;
-use fs::repository::GitFileStatus;
-use futures::StreamExt;
 use gpui::{
-    actions,
-    elements::*,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    impl_actions,
-    keymap_matcher::KeymapContext,
-    platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
-    Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-    ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
-    WindowContext,
+    actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext,
+    AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, FocusHandle,
+    FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render,
+    ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
+use parking_lot::Mutex;
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
+use settings::Settings;
 use std::{
     any::Any,
-    cell::RefCell,
-    cmp, mem,
+    cmp, fmt, mem,
     path::{Path, PathBuf},
     rc::Rc,
     sync::{
@@ -41,8 +26,14 @@ use std::{
         Arc,
     },
 };
-use theme::{Theme, ThemeSettings};
-use util::truncate_and_remove_front;
+use theme::ThemeSettings;
+
+use ui::{
+    prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label,
+    Tab, TabBar, TabPosition, Tooltip,
+};
+use ui::{v_stack, ContextMenu};
+use util::{maybe, truncate_and_remove_front, ResultExt};
 
 #[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
 #[serde(rename_all = "camelCase")]
@@ -63,26 +54,26 @@ pub enum SaveIntent {
     Skip,
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[derive(Clone, Deserialize, PartialEq, Debug)]
 pub struct ActivateItem(pub usize);
 
-#[derive(Clone, PartialEq)]
-pub struct CloseItemById {
-    pub item_id: usize,
-    pub pane: WeakViewHandle<Pane>,
-}
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemById {
+//     pub item_id: usize,
+//     pub pane: WeakView<Pane>,
+// }
 
-#[derive(Clone, PartialEq)]
-pub struct CloseItemsToTheLeftById {
-    pub item_id: usize,
-    pub pane: WeakViewHandle<Pane>,
-}
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheLeftById {
+//     pub item_id: usize,
+//     pub pane: WeakView<Pane>,
+// }
 
-#[derive(Clone, PartialEq)]
-pub struct CloseItemsToTheRightById {
-    pub item_id: usize,
-    pub pane: WeakViewHandle<Pane>,
-}
+// #[derive(Clone, PartialEq)]
+// pub struct CloseItemsToTheRightById {
+//     pub item_id: usize,
+//     pub pane: WeakView<Pane>,
+// }
 
 #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 #[serde(rename_all = "camelCase")]
@@ -90,7 +81,7 @@ pub struct CloseActiveItem {
     pub save_intent: Option<SaveIntent>,
 }
 
-#[derive(Clone, PartialEq, Debug, Deserialize)]
+#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 #[serde(rename_all = "camelCase")]
 pub struct CloseAllItems {
     pub save_intent: Option<SaveIntent>,
@@ -102,6 +93,16 @@ pub struct RevealInProjectPanel {
     pub entry_id: u64,
 }
 
+impl_actions!(
+    pane,
+    [
+        CloseAllItems,
+        CloseActiveItem,
+        ActivateItem,
+        RevealInProjectPanel
+    ]
+);
+
 actions!(
     pane,
     [
@@ -122,59 +123,13 @@ actions!(
     ]
 );
 
-impl_actions!(
-    pane,
-    [
-        ActivateItem,
-        CloseActiveItem,
-        CloseAllItems,
-        RevealInProjectPanel,
-    ]
-);
-
 const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
-pub type BackgroundActions = fn() -> &'static [(&'static str, &'static dyn Action)];
-
-pub fn init(cx: &mut AppContext) {
-    cx.add_action(Pane::toggle_zoom);
-    cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
-        pane.activate_item(action.0, true, true, cx);
-    });
-    cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
-        pane.activate_item(pane.items.len() - 1, true, true, cx);
-    });
-    cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
-        pane.activate_prev_item(true, cx);
-    });
-    cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
-        pane.activate_next_item(true, cx);
-    });
-    cx.add_async_action(Pane::close_active_item);
-    cx.add_async_action(Pane::close_inactive_items);
-    cx.add_async_action(Pane::close_clean_items);
-    cx.add_async_action(Pane::close_items_to_the_left);
-    cx.add_async_action(Pane::close_items_to_the_right);
-    cx.add_async_action(Pane::close_all_items);
-    cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
-    cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
-    cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
-    cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
-    cx.add_action(|pane: &mut Pane, action: &RevealInProjectPanel, cx| {
-        pane.project.update(cx, |_, cx| {
-            cx.emit(project::Event::RevealInProjectPanel(
-                ProjectEntryId::from_proto(action.entry_id),
-            ))
-        })
-    });
-}
-
-#[derive(Debug)]
 pub enum Event {
     AddItem { item: Box<dyn ItemHandle> },
     ActivateItem { local: bool },
     Remove,
-    RemoveItem { item_id: usize },
+    RemoveItem { item_id: EntityId },
     Split(SplitDirection),
     ChangeItemTitle,
     Focus,
@@ -182,41 +137,73 @@ pub enum Event {
     ZoomOut,
 }
 
+impl fmt::Debug for Event {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            Event::AddItem { item } => f
+                .debug_struct("AddItem")
+                .field("item", &item.item_id())
+                .finish(),
+            Event::ActivateItem { local } => f
+                .debug_struct("ActivateItem")
+                .field("local", local)
+                .finish(),
+            Event::Remove => f.write_str("Remove"),
+            Event::RemoveItem { item_id } => f
+                .debug_struct("RemoveItem")
+                .field("item_id", item_id)
+                .finish(),
+            Event::Split(direction) => f
+                .debug_struct("Split")
+                .field("direction", direction)
+                .finish(),
+            Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
+            Event::Focus => f.write_str("Focus"),
+            Event::ZoomIn => f.write_str("ZoomIn"),
+            Event::ZoomOut => f.write_str("ZoomOut"),
+        }
+    }
+}
+
 pub struct Pane {
+    focus_handle: FocusHandle,
     items: Vec<Box<dyn ItemHandle>>,
-    activation_history: Vec<usize>,
+    activation_history: Vec<EntityId>,
     zoomed: bool,
+    was_focused: bool,
     active_item_index: usize,
-    last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
-    autoscroll: bool,
+    last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
     nav_history: NavHistory,
-    toolbar: ViewHandle<Toolbar>,
-    tab_bar_context_menu: TabBarContextMenu,
-    tab_context_menu: ViewHandle<ContextMenu>,
-    _background_actions: BackgroundActions,
-    workspace: WeakViewHandle<Workspace>,
-    project: ModelHandle<Project>,
-    has_focus: bool,
-    can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
+    toolbar: View<Toolbar>,
+    new_item_menu: Option<View<ContextMenu>>,
+    split_item_menu: Option<View<ContextMenu>>,
+    //     tab_context_menu: View<ContextMenu>,
+    workspace: WeakView<Workspace>,
+    project: Model<Project>,
+    drag_split_direction: Option<SplitDirection>,
+    can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>>,
     can_split: bool,
-    render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
+    render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement>,
+    _subscriptions: Vec<Subscription>,
+    tab_bar_scroll_handle: ScrollHandle,
+    display_nav_history_buttons: bool,
 }
 
 pub struct ItemNavHistory {
     history: NavHistory,
-    item: Rc<dyn WeakItemHandle>,
+    item: Arc<dyn WeakItemHandle>,
 }
 
 #[derive(Clone)]
-pub struct NavHistory(Rc<RefCell<NavHistoryState>>);
+pub struct NavHistory(Arc<Mutex<NavHistoryState>>);
 
 struct NavHistoryState {
     mode: NavigationMode,
     backward_stack: VecDeque<NavigationEntry>,
     forward_stack: VecDeque<NavigationEntry>,
     closed_stack: VecDeque<NavigationEntry>,
-    paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
-    pane: WeakViewHandle<Pane>,
+    paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
+    pane: WeakView<Pane>,
     next_timestamp: Arc<AtomicUsize>,
 }
 
@@ -237,115 +224,135 @@ impl Default for NavigationMode {
 }
 
 pub struct NavigationEntry {
-    pub item: Rc<dyn WeakItemHandle>,
-    pub data: Option<Box<dyn Any>>,
+    pub item: Arc<dyn WeakItemHandle>,
+    pub data: Option<Box<dyn Any + Send>>,
     pub timestamp: usize,
 }
 
-pub struct DraggedItem {
-    pub handle: Box<dyn ItemHandle>,
-    pub pane: WeakViewHandle<Pane>,
-}
-
-pub enum ReorderBehavior {
-    None,
-    MoveAfterActive,
-    MoveToIndex(usize),
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum TabBarContextMenuKind {
-    New,
-    Split,
-}
-
-struct TabBarContextMenu {
-    kind: TabBarContextMenuKind,
-    handle: ViewHandle<ContextMenu>,
-}
-
-impl TabBarContextMenu {
-    fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<ViewHandle<ContextMenu>> {
-        if self.kind == kind {
-            return Some(self.handle.clone());
-        }
-        None
-    }
+#[derive(Clone)]
+pub struct DraggedTab {
+    pub pane: View<Pane>,
+    pub ix: usize,
+    pub item_id: EntityId,
+    pub detail: usize,
+    pub is_active: bool,
 }
 
-#[allow(clippy::too_many_arguments)]
-fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
-    svg_path: &'static str,
-    style: theme::Interactive<theme::IconButton>,
-    nav_button_height: f32,
-    tooltip_style: TooltipStyle,
-    enabled: bool,
-    on_click: F,
-    tooltip_action: A,
-    action_name: &str,
-    cx: &mut ViewContext<Pane>,
-) -> AnyElement<Pane> {
-    MouseEventHandler::new::<A, _>(0, cx, |state, _| {
-        let style = if enabled {
-            style.style_for(state)
-        } else {
-            style.disabled_style()
-        };
-        Svg::new(svg_path)
-            .with_color(style.color)
-            .constrained()
-            .with_width(style.icon_width)
-            .aligned()
-            .contained()
-            .with_style(style.container)
-            .constrained()
-            .with_width(style.button_width)
-            .with_height(nav_button_height)
-            .aligned()
-            .top()
-    })
-    .with_cursor_style(if enabled {
-        CursorStyle::PointingHand
-    } else {
-        CursorStyle::default()
-    })
-    .on_click(MouseButton::Left, move |_, toolbar, cx| {
-        on_click(toolbar, cx)
-    })
-    .with_tooltip::<A>(
-        0,
-        action_name.to_string(),
-        Some(Box::new(tooltip_action)),
-        tooltip_style,
-        cx,
-    )
-    .contained()
-    .into_any_named("nav button")
-}
+// pub struct DraggedItem {
+//     pub handle: Box<dyn ItemHandle>,
+//     pub pane: WeakView<Pane>,
+// }
+
+// pub enum ReorderBehavior {
+//     None,
+//     MoveAfterActive,
+//     MoveToIndex(usize),
+// }
+
+// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// enum TabBarContextMenuKind {
+//     New,
+//     Split,
+// }
+
+// struct TabBarContextMenu {
+//     kind: TabBarContextMenuKind,
+//     handle: View<ContextMenu>,
+// }
+
+// impl TabBarContextMenu {
+//     fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<View<ContextMenu>> {
+//         if self.kind == kind {
+//             return Some(self.handle.clone());
+//         }
+//         None
+//     }
+// }
+
+// #[allow(clippy::too_many_arguments)]
+// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
+//     svg_path: &'static str,
+//     style: theme2::Interactive<theme2::IconButton>,
+//     nav_button_height: f32,
+//     tooltip_style: TooltipStyle,
+//     enabled: bool,
+//     on_click: F,
+//     tooltip_action: A,
+//     action_name: &str,
+//     cx: &mut ViewContext<Pane>,
+// ) -> AnyElement<Pane> {
+//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
+//         let style = if enabled {
+//             style.style_for(state)
+//         } else {
+//             style.disabled_style()
+//         };
+//         Svg::new(svg_path)
+//             .with_color(style.color)
+//             .constrained()
+//             .with_width(style.icon_width)
+//             .aligned()
+//             .contained()
+//             .with_style(style.container)
+//             .constrained()
+//             .with_width(style.button_width)
+//             .with_height(nav_button_height)
+//             .aligned()
+//             .top()
+//     })
+//     .with_cursor_style(if enabled {
+//         CursorStyle::PointingHand
+//     } else {
+//         CursorStyle::default()
+//     })
+//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
+//         on_click(toolbar, cx)
+//     })
+//     .with_tooltip::<A>(
+//         0,
+//         action_name.to_string(),
+//         Some(Box::new(tooltip_action)),
+//         tooltip_style,
+//         cx,
+//     )
+//     .contained()
+//     .into_any_named("nav button")
+// }
+
+impl EventEmitter<Event> for Pane {}
 
 impl Pane {
     pub fn new(
-        workspace: WeakViewHandle<Workspace>,
-        project: ModelHandle<Project>,
-        background_actions: BackgroundActions,
+        workspace: WeakView<Workspace>,
+        project: Model<Project>,
         next_timestamp: Arc<AtomicUsize>,
+        can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        let pane_view_id = cx.view_id();
-        let handle = cx.weak_handle();
-        let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
-        context_menu.update(cx, |menu, _| {
-            menu.set_position_mode(OverlayPositionMode::Local)
-        });
-
+        // todo!("context menu")
+        // let pane_view_id = cx.view_id();
+        // let context_menu = cx.build_view(|cx| ContextMenu::new(pane_view_id, cx));
+        // context_menu.update(cx, |menu, _| {
+        //     menu.set_position_mode(OverlayPositionMode::Local)
+        // });
+        //
+        let focus_handle = cx.focus_handle();
+
+        let subscriptions = vec![
+            cx.on_focus_in(&focus_handle, move |this, cx| this.focus_in(cx)),
+            cx.on_focus_out(&focus_handle, move |this, cx| this.focus_out(cx)),
+        ];
+
+        let handle = cx.view().downgrade();
         Self {
+            focus_handle,
             items: Vec::new(),
             activation_history: Vec::new(),
+            was_focused: false,
             zoomed: false,
             active_item_index: 0,
             last_focused_view_by_item: Default::default(),
-            autoscroll: false,
-            nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
+            nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
                 mode: NavigationMode::Normal,
                 backward_stack: Default::default(),
                 forward_stack: Default::default(),
@@ -354,96 +361,151 @@ impl Pane {
                 pane: handle.clone(),
                 next_timestamp,
             }))),
-            toolbar: cx.add_view(|_| Toolbar::new()),
-            tab_bar_context_menu: TabBarContextMenu {
-                kind: TabBarContextMenuKind::New,
-                handle: context_menu,
-            },
-            tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
-            _background_actions: background_actions,
+            toolbar: cx.new_view(|_| Toolbar::new()),
+            new_item_menu: None,
+            split_item_menu: None,
+            tab_bar_scroll_handle: ScrollHandle::new(),
+            drag_split_direction: None,
+            // tab_bar_context_menu: TabBarContextMenu {
+            //     kind: TabBarContextMenuKind::New,
+            //     handle: context_menu,
+            // },
+            // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)),
             workspace,
             project,
-            has_focus: false,
-            can_drop: Rc::new(|_, _| true),
+            can_drop_predicate,
             can_split: true,
             render_tab_bar_buttons: Rc::new(move |pane, cx| {
-                Flex::row()
-                    // New menu
-                    .with_child(Self::render_tab_bar_button(
-                        0,
-                        "icons/plus.svg",
-                        false,
-                        Some(("New...".into(), None)),
-                        cx,
-                        |pane, cx| pane.deploy_new_menu(cx),
-                        |pane, cx| {
-                            pane.tab_bar_context_menu
-                                .handle
-                                .update(cx, |menu, _| menu.delay_cancel())
-                        },
-                        pane.tab_bar_context_menu
-                            .handle_if_kind(TabBarContextMenuKind::New),
-                    ))
-                    .with_child(Self::render_tab_bar_button(
-                        1,
-                        "icons/split.svg",
-                        false,
-                        Some(("Split Pane".into(), None)),
-                        cx,
-                        |pane, cx| pane.deploy_split_menu(cx),
-                        |pane, cx| {
-                            pane.tab_bar_context_menu
-                                .handle
-                                .update(cx, |menu, _| menu.delay_cancel())
-                        },
-                        pane.tab_bar_context_menu
-                            .handle_if_kind(TabBarContextMenuKind::Split),
-                    ))
-                    .with_child({
-                        let icon_path;
-                        let tooltip_label;
-                        if pane.is_zoomed() {
-                            icon_path = "icons/minimize.svg";
-                            tooltip_label = "Zoom In";
-                        } else {
-                            icon_path = "icons/maximize.svg";
-                            tooltip_label = "Zoom In";
-                        }
-
-                        Pane::render_tab_bar_button(
-                            2,
-                            icon_path,
-                            pane.is_zoomed(),
-                            Some((tooltip_label, Some(Box::new(ToggleZoom)))),
-                            cx,
-                            move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
-                            move |_, _| {},
-                            None,
-                        )
+                h_stack()
+                    .gap_2()
+                    .child(
+                        IconButton::new("plus", Icon::Plus)
+                            .icon_size(IconSize::Small)
+                            .icon_color(Color::Muted)
+                            .on_click(cx.listener(|pane, _, cx| {
+                                let menu = ContextMenu::build(cx, |menu, _| {
+                                    menu.action("New File", NewFile.boxed_clone())
+                                        .action("New Terminal", NewCenterTerminal.boxed_clone())
+                                        .action("New Search", NewSearch.boxed_clone())
+                                });
+                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
+                                    pane.focus(cx);
+                                    pane.new_item_menu = None;
+                                })
+                                .detach();
+                                pane.new_item_menu = Some(menu);
+                            }))
+                            .tooltip(|cx| Tooltip::text("New...", cx)),
+                    )
+                    .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
+                        el.child(Self::render_menu_overlay(new_item_menu))
+                    })
+                    .child(
+                        IconButton::new("split", Icon::Split)
+                            .icon_size(IconSize::Small)
+                            .icon_color(Color::Muted)
+                            .on_click(cx.listener(|pane, _, cx| {
+                                let menu = ContextMenu::build(cx, |menu, _| {
+                                    menu.action("Split Right", SplitRight.boxed_clone())
+                                        .action("Split Left", SplitLeft.boxed_clone())
+                                        .action("Split Up", SplitUp.boxed_clone())
+                                        .action("Split Down", SplitDown.boxed_clone())
+                                });
+                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
+                                    pane.focus(cx);
+                                    pane.split_item_menu = None;
+                                })
+                                .detach();
+                                pane.split_item_menu = Some(menu);
+                            }))
+                            .tooltip(|cx| Tooltip::text("Split Pane", cx)),
+                    )
+                    .child({
+                        let zoomed = pane.is_zoomed();
+                        IconButton::new("toggle_zoom", Icon::Maximize)
+                            .icon_size(IconSize::Small)
+                            .icon_color(Color::Muted)
+                            .selected(zoomed)
+                            .selected_icon(Icon::Minimize)
+                            .on_click(cx.listener(|pane, _, cx| {
+                                pane.toggle_zoom(&crate::ToggleZoom, cx);
+                            }))
+                            .tooltip(move |cx| {
+                                Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
+                            })
+                    })
+                    .when_some(pane.split_item_menu.as_ref(), |el, split_item_menu| {
+                        el.child(Self::render_menu_overlay(split_item_menu))
                     })
-                    .into_any()
+                    .into_any_element()
             }),
+            display_nav_history_buttons: true,
+            _subscriptions: subscriptions,
         }
     }
 
-    pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
-        &self.workspace
+    pub fn has_focus(&self, cx: &WindowContext) -> bool {
+        // todo!(); // inline this manually
+        self.focus_handle.contains_focused(cx)
+    }
+
+    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
+        if !self.was_focused {
+            self.was_focused = true;
+            cx.emit(Event::Focus);
+            cx.notify();
+        }
+
+        self.toolbar.update(cx, |toolbar, cx| {
+            toolbar.focus_changed(true, cx);
+        });
+
+        if let Some(active_item) = self.active_item() {
+            if self.focus_handle.is_focused(cx) {
+                // Pane was focused directly. We need to either focus a view inside the active item,
+                // or focus the active item itself
+                if let Some(weak_last_focused_view) =
+                    self.last_focused_view_by_item.get(&active_item.item_id())
+                {
+                    weak_last_focused_view.focus(cx);
+                    return;
+                }
+
+                active_item.focus_handle(cx).focus(cx);
+            } else if let Some(focused) = cx.focused() {
+                if !self.context_menu_focused(cx) {
+                    self.last_focused_view_by_item
+                        .insert(active_item.item_id(), focused);
+                }
+            }
+        }
     }
 
-    pub fn has_focus(&self) -> bool {
-        self.has_focus
+    fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
+        self.new_item_menu
+            .as_ref()
+            .or(self.split_item_menu.as_ref())
+            .map_or(false, |menu| menu.focus_handle(cx).is_focused(cx))
+    }
+
+    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
+        self.was_focused = false;
+        self.toolbar.update(cx, |toolbar, cx| {
+            toolbar.focus_changed(false, cx);
+        });
+        cx.notify();
     }
 
     pub fn active_item_index(&self) -> usize {
         self.active_item_index
     }
 
-    pub fn on_can_drop<F>(&mut self, can_drop: F)
-    where
-        F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
-    {
-        self.can_drop = Rc::new(can_drop);
-    }
+    //     pub fn on_can_drop<F>(&mut self, can_drop: F)
+    //     where
+    //         F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
+    //     {
+    //         self.can_drop = Rc::new(can_drop);
+    //     }
 
     pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
         self.can_split = can_split;
@@ -459,16 +521,16 @@ impl Pane {
 
     pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
     where
-        F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>,
+        F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement,
     {
         self.render_tab_bar_buttons = Rc::new(render);
         cx.notify();
     }
 
-    pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
+    pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
         ItemNavHistory {
             history: self.nav_history.clone(),
-            item: Rc::new(item.downgrade()),
+            item: Arc::new(item.downgrade()),
         }
     }
 
@@ -489,11 +551,33 @@ impl Pane {
     }
 
     pub fn can_navigate_backward(&self) -> bool {
-        !self.nav_history.0.borrow().backward_stack.is_empty()
+        !self.nav_history.0.lock().backward_stack.is_empty()
     }
 
     pub fn can_navigate_forward(&self) -> bool {
-        !self.nav_history.0.borrow().forward_stack.is_empty()
+        !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>) {
@@ -545,9 +629,9 @@ impl Pane {
                     let abs_path = project.absolute_path(&project_path, cx);
                     self.nav_history
                         .0
-                        .borrow_mut()
+                        .lock()
                         .paths_by_item
-                        .insert(item.id(), (project_path, abs_path));
+                        .insert(item.item_id(), (project_path, abs_path));
                 }
             }
         }
@@ -571,7 +655,7 @@ impl Pane {
         };
 
         let existing_item_index = self.items.iter().position(|existing_item| {
-            if existing_item.id() == item.id() {
+            if existing_item.item_id() == item.item_id() {
                 true
             } else if existing_item.is_singleton(cx) {
                 existing_item
@@ -636,17 +720,17 @@ impl Pane {
         self.items.iter()
     }
 
-    pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
+    pub fn items_of_type<T: Render>(&self) -> impl '_ + Iterator<Item = View<T>> {
         self.items
             .iter()
-            .filter_map(|item| item.as_any().clone().downcast())
+            .filter_map(|item| item.to_any().downcast().ok())
     }
 
     pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
         self.items.get(self.active_item_index).cloned()
     }
 
-    pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
+    pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
         self.items
             .get(self.active_item_index)?
             .pixel_position_of_cursor(cx)
@@ -667,19 +751,20 @@ impl Pane {
     }
 
     pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
-        self.items.iter().position(|i| i.id() == item.id())
+        self.items
+            .iter()
+            .position(|i| i.item_id() == item.item_id())
     }
 
-    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-        // Potentially warn the user of the new keybinding
-        let workspace_handle = self.workspace().clone();
-        cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) })
-            .detach();
+    pub fn item_for_index(&self, ix: usize) -> Option<&dyn ItemHandle> {
+        self.items.get(ix).map(|i| i.as_ref())
+    }
 
+    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
         if self.zoomed {
             cx.emit(Event::ZoomOut);
         } else if !self.items.is_empty() {
-            if !self.has_focus {
+            if !self.focus_handle.contains_focused(cx) {
                 cx.focus_self();
             }
             cx.emit(Event::ZoomIn);
@@ -712,18 +797,19 @@ impl Pane {
             if let Some(newly_active_item) = self.items.get(index) {
                 self.activation_history
                     .retain(|&previously_active_item_id| {
-                        previously_active_item_id != newly_active_item.id()
+                        previously_active_item_id != newly_active_item.item_id()
                     });
-                self.activation_history.push(newly_active_item.id());
+                self.activation_history.push(newly_active_item.item_id());
             }
 
             self.update_toolbar(cx);
+            self.update_status_bar(cx);
 
             if focus_item {
                 self.focus_active_item(cx);
             }
 
-            self.autoscroll = true;
+            self.tab_bar_scroll_handle.scroll_to_item(index);
             cx.notify();
         }
     }
@@ -756,7 +842,7 @@ impl Pane {
         if self.items.is_empty() {
             return None;
         }
-        let active_item_id = self.items[self.active_item_index].id();
+        let active_item_id = self.items[self.active_item_index].item_id();
         Some(self.close_item_by_id(
             active_item_id,
             action.save_intent.unwrap_or(SaveIntent::Close),
@@ -766,7 +852,7 @@ impl Pane {
 
     pub fn close_item_by_id(
         &mut self,
-        item_id_to_close: usize,
+        item_id_to_close: EntityId,
         save_intent: SaveIntent,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
@@ -782,7 +868,7 @@ impl Pane {
             return None;
         }
 
-        let active_item_id = self.items[self.active_item_index].id();
+        let active_item_id = self.items[self.active_item_index].item_id();
         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
             item_id != active_item_id
         }))
@@ -796,7 +882,7 @@ impl Pane {
         let item_ids: Vec<_> = self
             .items()
             .filter(|item| !item.is_dirty(cx))
-            .map(|item| item.id())
+            .map(|item| item.item_id())
             .collect();
         Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
             item_ids.contains(&item_id)
@@ -811,19 +897,19 @@ impl Pane {
         if self.items.is_empty() {
             return None;
         }
-        let active_item_id = self.items[self.active_item_index].id();
+        let active_item_id = self.items[self.active_item_index].item_id();
         Some(self.close_items_to_the_left_by_id(active_item_id, cx))
     }
 
     pub fn close_items_to_the_left_by_id(
         &mut self,
-        item_id: usize,
+        item_id: EntityId,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
         let item_ids: Vec<_> = self
             .items()
-            .take_while(|item| item.id() != item_id)
-            .map(|item| item.id())
+            .take_while(|item| item.item_id() != item_id)
+            .map(|item| item.item_id())
             .collect();
         self.close_items(cx, SaveIntent::Close, move |item_id| {
             item_ids.contains(&item_id)
@@ -838,20 +924,20 @@ impl Pane {
         if self.items.is_empty() {
             return None;
         }
-        let active_item_id = self.items[self.active_item_index].id();
+        let active_item_id = self.items[self.active_item_index].item_id();
         Some(self.close_items_to_the_right_by_id(active_item_id, cx))
     }
 
     pub fn close_items_to_the_right_by_id(
         &mut self,
-        item_id: usize,
+        item_id: EntityId,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>> {
         let item_ids: Vec<_> = self
             .items()
             .rev()
-            .take_while(|item| item.id() != item_id)
-            .map(|item| item.id())
+            .take_while(|item| item.item_id() != item_id)
+            .map(|item| item.item_id())
             .collect();
         self.close_items(cx, SaveIntent::Close, move |item_id| {
             item_ids.contains(&item_id)
@@ -913,13 +999,13 @@ impl Pane {
         &mut self,
         cx: &mut ViewContext<Pane>,
         mut save_intent: SaveIntent,
-        should_close: impl 'static + Fn(usize) -> bool,
+        should_close: impl Fn(EntityId) -> bool,
     ) -> Task<Result<()>> {
         // Find the items to close.
         let mut items_to_close = Vec::new();
         let mut dirty_items = Vec::new();
         for item in &self.items {
-            if should_close(item.id()) {
+            if should_close(item.item_id()) {
                 items_to_close.push(item.boxed_clone());
                 if item.is_dirty(cx) {
                     dirty_items.push(item.boxed_clone());
@@ -936,7 +1022,7 @@ impl Pane {
         let workspace = self.workspace.clone();
         cx.spawn(|pane, mut cx| async move {
             if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-                let mut answer = pane.update(&mut cx, |_, cx| {
+                let answer = pane.update(&mut cx, |_, cx| {
                     let prompt =
                         Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
                     cx.prompt(
@@ -945,9 +1031,9 @@ impl Pane {
                         &["Save all", "Discard all", "Cancel"],
                     )
                 })?;
-                match answer.next().await {
-                    Some(0) => save_intent = SaveIntent::SaveAll,
-                    Some(1) => save_intent = SaveIntent::Skip,
+                match answer.await {
+                    Ok(0) => save_intent = SaveIntent::SaveAll,
+                    Ok(1) => save_intent = SaveIntent::Skip,
                     _ => {}
                 }
             }

crates/workspace/src/pane/dragged_item_receiver.rs πŸ”—

@@ -1,15 +1,14 @@
 use super::DraggedItem;
 use crate::{Pane, SplitDirection, Workspace};
-use drag_and_drop::DragAndDrop;
 use gpui::{
     color::Color,
-    elements::{Canvas, MouseEventHandler, ParentElement, Stack},
+    elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
     geometry::{rect::RectF, vector::Vector2F},
     platform::MouseButton,
     scene::MouseUp,
     AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
 };
-use project::ProjectEntryId;
+use project2::ProjectEntryId;
 
 pub fn dragged_item_receiver<Tag, D, F>(
     pane: &Pane,
@@ -236,5 +235,5 @@ fn drop_split_direction(
 }
 
 fn overlay_color(cx: &AppContext) -> Color {
-    theme::current(cx).workspace.drop_target_overlay_color
+    theme2::current(cx).workspace.drop_target_overlay_color
 }

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

@@ -1,23 +1,22 @@
-use crate::{pane_group::element::PaneAxisElement, AppState, FollowerState, Pane, Workspace};
+use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
 use anyhow::{anyhow, Result};
 use call::{ActiveCall, ParticipantLocation};
 use collections::HashMap;
 use gpui::{
-    elements::*,
-    geometry::{rect::RectF, vector::Vector2F},
-    platform::{CursorStyle, MouseButton},
-    AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
+    point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
+    ViewContext,
 };
+use parking_lot::Mutex;
 use project::Project;
 use serde::Deserialize;
-use std::{cell::RefCell, rc::Rc, sync::Arc};
-use theme::Theme;
+use std::sync::Arc;
+use ui::{prelude::*, Button};
 
-const HANDLE_HITBOX_SIZE: f32 = 4.0;
+const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
 const HORIZONTAL_MIN_SIZE: f32 = 80.;
 const VERTICAL_MIN_SIZE: f32 = 100.;
 
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone)]
 pub struct PaneGroup {
     pub(crate) root: Member,
 }
@@ -27,7 +26,7 @@ impl PaneGroup {
         Self { root }
     }
 
-    pub fn new(pane: ViewHandle<Pane>) -> Self {
+    pub fn new(pane: View<Pane>) -> Self {
         Self {
             root: Member::Pane(pane),
         }
@@ -35,8 +34,8 @@ impl PaneGroup {
 
     pub fn split(
         &mut self,
-        old_pane: &ViewHandle<Pane>,
-        new_pane: &ViewHandle<Pane>,
+        old_pane: &View<Pane>,
+        new_pane: &View<Pane>,
         direction: SplitDirection,
     ) -> Result<()> {
         match &mut self.root {
@@ -52,14 +51,14 @@ impl PaneGroup {
         }
     }
 
-    pub fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
+    pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
         match &self.root {
             Member::Pane(_) => None,
             Member::Axis(axis) => axis.bounding_box_for_pane(pane),
         }
     }
 
-    pub fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
+    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
         match &self.root {
             Member::Pane(pane) => Some(pane),
             Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
@@ -70,7 +69,7 @@ impl PaneGroup {
     /// - Ok(true) if it found and removed a pane
     /// - Ok(false) if it found but did not remove the pane
     /// - Err(_) if it did not find the pane
-    pub fn remove(&mut self, pane: &ViewHandle<Pane>) -> Result<bool> {
+    pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
         match &mut self.root {
             Member::Pane(_) => Ok(false),
             Member::Axis(axis) => {
@@ -82,7 +81,7 @@ impl PaneGroup {
         }
     }
 
-    pub fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
+    pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
         match &mut self.root {
             Member::Pane(_) => {}
             Member::Axis(axis) => axis.swap(from, to),
@@ -91,19 +90,17 @@ impl PaneGroup {
 
     pub(crate) fn render(
         &self,
-        project: &ModelHandle<Project>,
-        theme: &Theme,
-        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
-        active_call: Option<&ModelHandle<ActiveCall>>,
-        active_pane: &ViewHandle<Pane>,
-        zoomed: Option<&AnyViewHandle>,
+        project: &Model<Project>,
+        follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Model<ActiveCall>>,
+        active_pane: &View<Pane>,
+        zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> AnyElement<Workspace> {
+    ) -> impl IntoElement {
         self.root.render(
             project,
             0,
-            theme,
             follower_states,
             active_call,
             active_pane,
@@ -113,25 +110,25 @@ impl PaneGroup {
         )
     }
 
-    pub(crate) fn panes(&self) -> Vec<&ViewHandle<Pane>> {
+    pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
         let mut panes = Vec::new();
         self.root.collect_panes(&mut panes);
         panes
     }
+
+    pub(crate) fn first_pane(&self) -> View<Pane> {
+        self.root.first_pane()
+    }
 }
 
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone)]
 pub(crate) enum Member {
     Axis(PaneAxis),
-    Pane(ViewHandle<Pane>),
+    Pane(View<Pane>),
 }
 
 impl Member {
-    fn new_axis(
-        old_pane: ViewHandle<Pane>,
-        new_pane: ViewHandle<Pane>,
-        direction: SplitDirection,
-    ) -> Self {
+    fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
         use Axis::*;
         use SplitDirection::*;
 
@@ -148,52 +145,52 @@ impl Member {
         Member::Axis(PaneAxis::new(axis, members))
     }
 
-    fn contains(&self, needle: &ViewHandle<Pane>) -> bool {
+    fn contains(&self, needle: &View<Pane>) -> bool {
         match self {
             Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
             Member::Pane(pane) => pane == needle,
         }
     }
 
+    fn first_pane(&self) -> View<Pane> {
+        match self {
+            Member::Axis(axis) => axis.members[0].first_pane(),
+            Member::Pane(pane) => pane.clone(),
+        }
+    }
+
     pub fn render(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         basis: usize,
-        theme: &Theme,
-        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
-        active_call: Option<&ModelHandle<ActiveCall>>,
-        active_pane: &ViewHandle<Pane>,
-        zoomed: Option<&AnyViewHandle>,
+        follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Model<ActiveCall>>,
+        active_pane: &View<Pane>,
+        zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> AnyElement<Workspace> {
-        enum FollowIntoExternalProject {}
-
+    ) -> impl IntoElement {
         match self {
             Member::Pane(pane) => {
-                let pane_element = if Some(&**pane) == zoomed {
-                    Empty::new().into_any()
-                } else {
-                    ChildView::new(pane, cx).into_any()
-                };
+                if zoomed == Some(&pane.downgrade().into()) {
+                    return div().into_any();
+                }
 
                 let leader = follower_states.get(pane).and_then(|state| {
                     let room = active_call?.read(cx).room()?.read(cx);
                     room.remote_participant_for_peer_id(state.leader_id)
                 });
 
-                let mut leader_border = Border::default();
+                let mut leader_border = None;
                 let mut leader_status_box = None;
                 if let Some(leader) = &leader {
-                    let leader_color = theme
-                        .editor
-                        .selection_style_for_room_participant(leader.participant_index.0)
+                    let mut leader_color = cx
+                        .theme()
+                        .players()
+                        .color_for_participant(leader.participant_index.0)
                         .cursor;
-                    leader_border = Border::all(theme.workspace.leader_border_width, leader_color);
-                    leader_border
-                        .color
-                        .fade_out(1. - theme.workspace.leader_border_opacity);
-                    leader_border.overlay = true;
+                    leader_color.fade_out(0.3);
+                    leader_border = Some(leader_color);
 
                     leader_status_box = match leader.location {
                         ParticipantLocation::SharedProject {
@@ -205,97 +202,90 @@ impl Member {
                                 let leader_user = leader.user.clone();
                                 let leader_user_id = leader.user.id;
                                 Some(
-                                    MouseEventHandler::new::<FollowIntoExternalProject, _>(
-                                        pane.id(),
-                                        cx,
-                                        |_, _| {
-                                            Label::new(
-                                                format!(
-                                                    "Follow {} to their active project",
-                                                    leader_user.github_login,
-                                                ),
-                                                theme
-                                                    .workspace
-                                                    .external_location_message
-                                                    .text
-                                                    .clone(),
-                                            )
-                                            .contained()
-                                            .with_style(
-                                                theme.workspace.external_location_message.container,
+                                    Button::new(
+                                        ("leader-status", pane.entity_id()),
+                                        format!(
+                                            "Follow {} to their active project",
+                                            leader_user.github_login,
+                                        ),
+                                    )
+                                    .on_click(cx.listener(
+                                        move |this, _, cx| {
+                                            crate::join_remote_project(
+                                                leader_project_id,
+                                                leader_user_id,
+                                                this.app_state().clone(),
+                                                cx,
                                             )
+                                            .detach_and_log_err(cx);
                                         },
-                                    )
-                                    .with_cursor_style(CursorStyle::PointingHand)
-                                    .on_click(MouseButton::Left, move |_, this, cx| {
-                                        crate::join_remote_project(
-                                            leader_project_id,
-                                            leader_user_id,
-                                            this.app_state().clone(),
-                                            cx,
-                                        )
-                                        .detach_and_log_err(cx);
-                                    })
-                                    .aligned()
-                                    .bottom()
-                                    .right()
-                                    .into_any(),
+                                    )),
                                 )
                             }
                         }
-                        ParticipantLocation::UnsharedProject => Some(
-                            Label::new(
-                                format!(
-                                    "{} is viewing an unshared Zed project",
-                                    leader.user.github_login
-                                ),
-                                theme.workspace.external_location_message.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.workspace.external_location_message.container)
-                            .aligned()
-                            .bottom()
-                            .right()
-                            .into_any(),
-                        ),
-                        ParticipantLocation::External => Some(
-                            Label::new(
-                                format!(
-                                    "{} is viewing a window outside of Zed",
-                                    leader.user.github_login
-                                ),
-                                theme.workspace.external_location_message.text.clone(),
-                            )
-                            .contained()
-                            .with_style(theme.workspace.external_location_message.container)
-                            .aligned()
-                            .bottom()
-                            .right()
-                            .into_any(),
-                        ),
+                        ParticipantLocation::UnsharedProject => Some(Button::new(
+                            ("leader-status", pane.entity_id()),
+                            format!(
+                                "{} is viewing an unshared Zed project",
+                                leader.user.github_login
+                            ),
+                        )),
+                        ParticipantLocation::External => Some(Button::new(
+                            ("leader-status", pane.entity_id()),
+                            format!(
+                                "{} is viewing a window outside of Zed",
+                                leader.user.github_login
+                            ),
+                        )),
                     };
                 }
 
-                Stack::new()
-                    .with_child(pane_element.contained().with_border(leader_border))
-                    .with_children(leader_status_box)
+                div()
+                    .relative()
+                    .flex_1()
+                    .size_full()
+                    .child(pane.clone())
+                    .when_some(leader_border, |this, color| {
+                        this.border_2().border_color(color)
+                    })
+                    .when_some(leader_status_box, |this, status_box| {
+                        this.child(
+                            div()
+                                .absolute()
+                                .w_96()
+                                .bottom_3()
+                                .right_3()
+                                .z_index(1)
+                                .child(status_box),
+                        )
+                    })
                     .into_any()
+
+                // let el = div()
+                //     .flex()
+                //     .flex_1()
+                //     .gap_px()
+                //     .w_full()
+                //     .h_full()
+                //     .bg(cx.theme().colors().editor)
+                //     .children();
             }
-            Member::Axis(axis) => axis.render(
-                project,
-                basis + 1,
-                theme,
-                follower_states,
-                active_call,
-                active_pane,
-                zoomed,
-                app_state,
-                cx,
-            ),
+            Member::Axis(axis) => axis
+                .render(
+                    project,
+                    basis + 1,
+                    follower_states,
+                    active_call,
+                    active_pane,
+                    zoomed,
+                    app_state,
+                    cx,
+                )
+                .into_any(),
         }
     }
 
-    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a ViewHandle<Pane>>) {
+    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
         match self {
             Member::Axis(axis) => {
                 for member in &axis.members {
@@ -307,18 +297,18 @@ impl Member {
     }
 }
 
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone)]
 pub(crate) struct PaneAxis {
     pub axis: Axis,
     pub members: Vec<Member>,
-    pub flexes: Rc<RefCell<Vec<f32>>>,
-    pub bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
+    pub flexes: Arc<Mutex<Vec<f32>>>,
+    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
 }
 
 impl PaneAxis {
     pub fn new(axis: Axis, members: Vec<Member>) -> Self {
-        let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
-        let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
+        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
+        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
         Self {
             axis,
             members,
@@ -331,8 +321,8 @@ impl PaneAxis {
         let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
         debug_assert!(members.len() == flexes.len());
 
-        let flexes = Rc::new(RefCell::new(flexes));
-        let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
+        let flexes = Arc::new(Mutex::new(flexes));
+        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
         Self {
             axis,
             members,
@@ -343,8 +333,8 @@ impl PaneAxis {
 
     fn split(
         &mut self,
-        old_pane: &ViewHandle<Pane>,
-        new_pane: &ViewHandle<Pane>,
+        old_pane: &View<Pane>,
+        new_pane: &View<Pane>,
         direction: SplitDirection,
     ) -> Result<()> {
         for (mut idx, member) in self.members.iter_mut().enumerate() {
@@ -362,7 +352,7 @@ impl PaneAxis {
                             }
 
                             self.members.insert(idx, Member::Pane(new_pane.clone()));
-                            *self.flexes.borrow_mut() = vec![1.; self.members.len()];
+                            *self.flexes.lock() = vec![1.; self.members.len()];
                         } else {
                             *member =
                                 Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
@@ -375,7 +365,7 @@ impl PaneAxis {
         Err(anyhow!("Pane not found"))
     }
 
-    fn remove(&mut self, pane_to_remove: &ViewHandle<Pane>) -> Result<Option<Member>> {
+    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
         let mut found_pane = false;
         let mut remove_member = None;
         for (idx, member) in self.members.iter_mut().enumerate() {
@@ -402,12 +392,12 @@ impl PaneAxis {
         if found_pane {
             if let Some(idx) = remove_member {
                 self.members.remove(idx);
-                *self.flexes.borrow_mut() = vec![1.; self.members.len()];
+                *self.flexes.lock() = vec![1.; self.members.len()];
             }
 
             if self.members.len() == 1 {
                 let result = self.members.pop();
-                *self.flexes.borrow_mut() = vec![1.; self.members.len()];
+                *self.flexes.lock() = vec![1.; self.members.len()];
                 Ok(result)
             } else {
                 Ok(None)
@@ -417,7 +407,7 @@ impl PaneAxis {
         }
     }
 
-    fn swap(&mut self, from: &ViewHandle<Pane>, to: &ViewHandle<Pane>) {
+    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
         for member in self.members.iter_mut() {
             match member {
                 Member::Axis(axis) => axis.swap(from, to),
@@ -432,14 +422,14 @@ impl PaneAxis {
         }
     }
 
-    fn bounding_box_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<RectF> {
-        debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
+    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
+        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 
         for (idx, member) in self.members.iter().enumerate() {
             match member {
                 Member::Pane(found) => {
                     if pane == found {
-                        return self.bounding_boxes.borrow()[idx];
+                        return self.bounding_boxes.lock()[idx];
                     }
                 }
                 Member::Axis(axis) => {
@@ -452,14 +442,14 @@ impl PaneAxis {
         None
     }
 
-    fn pane_at_pixel_position(&self, coordinate: Vector2F) -> Option<&ViewHandle<Pane>> {
-        debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
+    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
+        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
 
-        let bounding_boxes = self.bounding_boxes.borrow();
+        let bounding_boxes = self.bounding_boxes.lock();
 
         for (idx, member) in self.members.iter().enumerate() {
             if let Some(coordinates) = bounding_boxes[idx] {
-                if coordinates.contains_point(coordinate) {
+                if coordinates.contains(&coordinate) {
                     return match member {
                         Member::Pane(found) => Some(found),
                         Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
@@ -472,65 +462,43 @@ impl PaneAxis {
 
     fn render(
         &self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         basis: usize,
-        theme: &Theme,
-        follower_states: &HashMap<ViewHandle<Pane>, FollowerState>,
-        active_call: Option<&ModelHandle<ActiveCall>>,
-        active_pane: &ViewHandle<Pane>,
-        zoomed: Option<&AnyViewHandle>,
+        follower_states: &HashMap<View<Pane>, FollowerState>,
+        active_call: Option<&Model<ActiveCall>>,
+        active_pane: &View<Pane>,
+        zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> AnyElement<Workspace> {
-        debug_assert!(self.members.len() == self.flexes.borrow().len());
+    ) -> gpui::AnyElement {
+        debug_assert!(self.members.len() == self.flexes.lock().len());
+        let mut active_pane_ix = None;
 
-        let mut pane_axis = PaneAxisElement::new(
+        pane_axis(
             self.axis,
             basis,
             self.flexes.clone(),
             self.bounding_boxes.clone(),
-        );
-        let mut active_pane_ix = None;
-
-        let mut members = self.members.iter().enumerate().peekable();
-        while let Some((ix, member)) = members.next() {
-            let last = members.peek().is_none();
-
+        )
+        .children(self.members.iter().enumerate().map(|(ix, member)| {
             if member.contains(active_pane) {
                 active_pane_ix = Some(ix);
             }
-
-            let mut member = member.render(
-                project,
-                (basis + ix) * 10,
-                theme,
-                follower_states,
-                active_call,
-                active_pane,
-                zoomed,
-                app_state,
-                cx,
-            );
-
-            if !last {
-                let mut border = theme.workspace.pane_divider;
-                border.left = false;
-                border.right = false;
-                border.top = false;
-                border.bottom = false;
-
-                match self.axis {
-                    Axis::Vertical => border.bottom = true,
-                    Axis::Horizontal => border.right = true,
-                }
-
-                member = member.contained().with_border(border).into_any();
-            }
-
-            pane_axis = pane_axis.with_child(member.into_any());
-        }
-        pane_axis.set_active_pane(active_pane_ix);
-        pane_axis.into_any()
+            member
+                .render(
+                    project,
+                    (basis + ix) * 10,
+                    follower_states,
+                    active_call,
+                    active_pane,
+                    zoomed,
+                    app_state,
+                    cx,
+                )
+                .into_any_element()
+        }))
+        .with_active_pane(active_pane_ix)
+        .into_any_element()
     }
 }
 
@@ -547,28 +515,33 @@ impl SplitDirection {
         [Self::Up, Self::Down, Self::Left, Self::Right]
     }
 
-    pub fn edge(&self, rect: RectF) -> f32 {
+    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
         match self {
-            Self::Up => rect.min_y(),
-            Self::Down => rect.max_y(),
-            Self::Left => rect.min_x(),
-            Self::Right => rect.max_x(),
+            Self::Up => rect.origin.y,
+            Self::Down => rect.lower_left().y,
+            Self::Left => rect.lower_left().x,
+            Self::Right => rect.lower_right().x,
         }
     }
 
-    // Returns a new rectangle which shares an edge in SplitDirection and has `size` along SplitDirection
-    pub fn along_edge(&self, rect: RectF, size: f32) -> RectF {
+    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
         match self {
-            Self::Up => RectF::new(rect.origin(), Vector2F::new(rect.width(), size)),
-            Self::Down => RectF::new(
-                rect.lower_left() - Vector2F::new(0., size),
-                Vector2F::new(rect.width(), size),
-            ),
-            Self::Left => RectF::new(rect.origin(), Vector2F::new(size, rect.height())),
-            Self::Right => RectF::new(
-                rect.upper_right() - Vector2F::new(size, 0.),
-                Vector2F::new(size, rect.height()),
-            ),
+            Self::Up => Bounds {
+                origin: bounds.origin,
+                size: size(bounds.size.width, length),
+            },
+            Self::Down => Bounds {
+                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
+                size: size(bounds.size.width, length),
+            },
+            Self::Left => Bounds {
+                origin: bounds.origin,
+                size: size(length, bounds.size.height),
+            },
+            Self::Right => Bounds {
+                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
+                size: size(length, bounds.size.height),
+            },
         }
     }
 
@@ -588,402 +561,305 @@ impl SplitDirection {
 }
 
 mod element {
-    use std::{cell::RefCell, iter::from_fn, ops::Range, rc::Rc};
+
+    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
 
     use gpui::{
-        geometry::{
-            rect::RectF,
-            vector::{vec2f, Vector2F},
-        },
-        json::{self, ToJson},
-        platform::{CursorStyle, MouseButton},
-        scene::MouseDrag,
-        AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
-        SizeConstraint, Vector2FExt, ViewContext,
+        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
+        IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
+        Size, Style, WindowContext,
     };
+    use parking_lot::Mutex;
+    use smallvec::SmallVec;
+    use ui::prelude::*;
 
-    use crate::{
-        pane_group::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE},
-        Workspace, WorkspaceSettings,
-    };
+    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
+
+    const DIVIDER_SIZE: f32 = 1.0;
+
+    pub fn pane_axis(
+        axis: Axis,
+        basis: usize,
+        flexes: Arc<Mutex<Vec<f32>>>,
+        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
+    ) -> PaneAxisElement {
+        PaneAxisElement {
+            axis,
+            basis,
+            flexes,
+            bounding_boxes,
+            children: SmallVec::new(),
+            active_pane_ix: None,
+        }
+    }
 
     pub struct PaneAxisElement {
         axis: Axis,
         basis: usize,
+        flexes: Arc<Mutex<Vec<f32>>>,
+        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
+        children: SmallVec<[AnyElement; 2]>,
         active_pane_ix: Option<usize>,
-        flexes: Rc<RefCell<Vec<f32>>>,
-        children: Vec<AnyElement<Workspace>>,
-        bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
     }
 
     impl PaneAxisElement {
-        pub fn new(
-            axis: Axis,
-            basis: usize,
-            flexes: Rc<RefCell<Vec<f32>>>,
-            bounding_boxes: Rc<RefCell<Vec<Option<RectF>>>>,
-        ) -> Self {
-            Self {
-                axis,
-                basis,
-                flexes,
-                bounding_boxes,
-                active_pane_ix: None,
-                children: Default::default(),
-            }
-        }
-
-        pub fn set_active_pane(&mut self, active_pane_ix: Option<usize>) {
+        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
             self.active_pane_ix = active_pane_ix;
+            self
         }
 
-        fn layout_children(
-            &mut self,
-            active_pane_magnification: f32,
-            constraint: SizeConstraint,
-            remaining_space: &mut f32,
-            remaining_flex: &mut f32,
-            cross_axis_max: &mut f32,
-            view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
+        fn compute_resize(
+            flexes: &Arc<Mutex<Vec<f32>>>,
+            e: &MouseMoveEvent,
+            ix: usize,
+            axis: Axis,
+            child_start: Point<Pixels>,
+            container_size: Size<Pixels>,
+            cx: &mut WindowContext,
         ) {
-            let flexes = self.flexes.borrow();
-            let cross_axis = self.axis.invert();
-            for (ix, child) in self.children.iter_mut().enumerate() {
-                let flex = if active_pane_magnification != 1. {
-                    if let Some(active_pane_ix) = self.active_pane_ix {
-                        if ix == active_pane_ix {
-                            active_pane_magnification
-                        } else {
-                            1.
-                        }
-                    } else {
-                        1.
-                    }
-                } else {
-                    flexes[ix]
-                };
+            let min_size = match axis {
+                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
+                Axis::Vertical => px(VERTICAL_MIN_SIZE),
+            };
+            let mut flexes = flexes.lock();
+            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
 
-                let child_size = if *remaining_flex == 0.0 {
-                    *remaining_space
-                } else {
-                    let space_per_flex = *remaining_space / *remaining_flex;
-                    space_per_flex * flex
-                };
+            let size = move |ix, flexes: &[f32]| {
+                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
+            };
 
-                let child_constraint = match self.axis {
-                    Axis::Horizontal => SizeConstraint::new(
-                        vec2f(child_size, constraint.min.y()),
-                        vec2f(child_size, constraint.max.y()),
-                    ),
-                    Axis::Vertical => SizeConstraint::new(
-                        vec2f(constraint.min.x(), child_size),
-                        vec2f(constraint.max.x(), child_size),
-                    ),
-                };
-                let child_size = child.layout(child_constraint, view, cx);
-                *remaining_space -= child_size.along(self.axis);
-                *remaining_flex -= flex;
-                *cross_axis_max = cross_axis_max.max(child_size.along(cross_axis));
+            // Don't allow resizing to less than the minimum size, if elements are already too small
+            if min_size - px(1.) > size(ix, flexes.as_slice()) {
+                return;
             }
-        }
 
-        fn handle_resize(
-            flexes: Rc<RefCell<Vec<f32>>>,
-            axis: Axis,
-            preceding_ix: usize,
-            child_start: Vector2F,
-            drag_bounds: RectF,
-        ) -> impl Fn(MouseDrag, &mut Workspace, &mut EventContext<Workspace>) {
-            let size = move |ix, flexes: &[f32]| {
-                drag_bounds.length_along(axis) * (flexes[ix] / flexes.len() as f32)
+            let mut proposed_current_pixel_change =
+                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
+
+            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
+                let flex_change = pixel_dx / container_size.along(axis);
+                let current_target_flex = flexes[target_ix] + flex_change;
+                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
+                (current_target_flex, next_target_flex)
             };
 
-            move |drag, workspace: &mut Workspace, cx| {
-                if drag.end {
-                    // TODO: Clear cascading resize state
-                    return;
-                }
-                let min_size = match axis {
-                    Axis::Horizontal => HORIZONTAL_MIN_SIZE,
-                    Axis::Vertical => VERTICAL_MIN_SIZE,
-                };
-                let mut flexes = flexes.borrow_mut();
+            let mut successors = iter::from_fn({
+                let forward = proposed_current_pixel_change > px(0.);
+                let mut ix_offset = 0;
+                let len = flexes.len();
+                move || {
+                    let result = if forward {
+                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
+                    } else {
+                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
+                    };
 
-                // Don't allow resizing to less than the minimum size, if elements are already too small
-                if min_size - 1. > size(preceding_ix, flexes.as_slice()) {
-                    return;
-                }
+                    ix_offset += 1;
 
-                let mut proposed_current_pixel_change = (drag.position - child_start).along(axis)
-                    - size(preceding_ix, flexes.as_slice());
+                    result
+                }
+            });
 
-                let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-                    let flex_change = pixel_dx / drag_bounds.length_along(axis);
-                    let current_target_flex = flexes[target_ix] + flex_change;
-                    let next_target_flex =
-                        flexes[(target_ix as isize + next) as usize] - flex_change;
-                    (current_target_flex, next_target_flex)
+            while proposed_current_pixel_change.abs() > px(0.) {
+                let Some(current_ix) = successors.next() else {
+                    break;
                 };
 
-                let mut successors = from_fn({
-                    let forward = proposed_current_pixel_change > 0.;
-                    let mut ix_offset = 0;
-                    let len = flexes.len();
-                    move || {
-                        let result = if forward {
-                            (preceding_ix + 1 + ix_offset < len).then(|| preceding_ix + ix_offset)
-                        } else {
-                            (preceding_ix as isize - ix_offset as isize >= 0)
-                                .then(|| preceding_ix - ix_offset)
-                        };
-
-                        ix_offset += 1;
+                let next_target_size = Pixels::max(
+                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
+                    min_size,
+                );
 
-                        result
-                    }
-                });
+                let current_target_size = Pixels::max(
+                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
+                        - next_target_size,
+                    min_size,
+                );
 
-                while proposed_current_pixel_change.abs() > 0. {
-                    let Some(current_ix) = successors.next() else {
-                        break;
-                    };
+                let current_pixel_change =
+                    current_target_size - size(current_ix, flexes.as_slice());
 
-                    let next_target_size = f32::max(
-                        size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
-                        min_size,
-                    );
+                let (current_target_flex, next_target_flex) =
+                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
 
-                    let current_target_size = f32::max(
-                        size(current_ix, flexes.as_slice())
-                            + size(current_ix + 1, flexes.as_slice())
-                            - next_target_size,
-                        min_size,
-                    );
+                flexes[current_ix] = current_target_flex;
+                flexes[current_ix + 1] = next_target_flex;
 
-                    let current_pixel_change =
-                        current_target_size - size(current_ix, flexes.as_slice());
+                proposed_current_pixel_change -= current_pixel_change;
+            }
 
-                    let (current_target_flex, next_target_flex) =
-                        flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
+            // todo!(schedule serialize)
+            // workspace.schedule_serialize(cx);
+            cx.notify();
+        }
 
-                    flexes[current_ix] = current_target_flex;
-                    flexes[current_ix + 1] = next_target_flex;
+        fn push_handle(
+            flexes: Arc<Mutex<Vec<f32>>>,
+            dragged_handle: Rc<RefCell<Option<usize>>>,
+            axis: Axis,
+            ix: usize,
+            pane_bounds: Bounds<Pixels>,
+            axis_bounds: Bounds<Pixels>,
+            cx: &mut WindowContext,
+        ) {
+            let handle_bounds = Bounds {
+                origin: pane_bounds.origin.apply_along(axis, |origin| {
+                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
+                }),
+                size: pane_bounds
+                    .size
+                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
+            };
+            let divider_bounds = Bounds {
+                origin: pane_bounds
+                    .origin
+                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
+                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
+            };
 
-                    proposed_current_pixel_change -= current_pixel_change;
+            cx.with_z_index(3, |cx| {
+                let interactive_handle_bounds = InteractiveBounds {
+                    bounds: handle_bounds,
+                    stacking_order: cx.stacking_order().clone(),
+                };
+                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
+                    cx.set_cursor_style(match axis {
+                        Axis::Vertical => CursorStyle::ResizeUpDown,
+                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
+                    })
                 }
 
-                workspace.schedule_serialize(cx);
-                cx.notify();
-            }
-        }
-    }
+                cx.add_opaque_layer(handle_bounds);
+                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
 
-    impl Extend<AnyElement<Workspace>> for PaneAxisElement {
-        fn extend<T: IntoIterator<Item = AnyElement<Workspace>>>(&mut self, children: T) {
-            self.children.extend(children);
+                cx.on_mouse_event({
+                    let dragged_handle = dragged_handle.clone();
+                    move |e: &MouseDownEvent, phase, _cx| {
+                        if phase.bubble() && handle_bounds.contains(&e.position) {
+                            dragged_handle.replace(Some(ix));
+                        }
+                    }
+                });
+                cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
+                    let dragged_handle = dragged_handle.borrow();
+                    if phase.bubble() && *dragged_handle == Some(ix) {
+                        Self::compute_resize(
+                            &flexes,
+                            e,
+                            ix,
+                            axis,
+                            pane_bounds.origin,
+                            axis_bounds.size,
+                            cx,
+                        )
+                    }
+                });
+            });
         }
     }
 
-    impl Element<Workspace> for PaneAxisElement {
-        type LayoutState = f32;
-        type PaintState = ();
-
-        fn layout(
-            &mut self,
-            constraint: SizeConstraint,
-            view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
-        ) -> (Vector2F, Self::LayoutState) {
-            debug_assert!(self.children.len() == self.flexes.borrow().len());
-
-            let active_pane_magnification =
-                settings::get::<WorkspaceSettings>(cx).active_pane_magnification;
-
-            let mut remaining_flex = 0.;
-
-            if active_pane_magnification != 1. {
-                let active_pane_flex = self
-                    .active_pane_ix
-                    .map(|_| active_pane_magnification)
-                    .unwrap_or(1.);
-                remaining_flex += self.children.len() as f32 - 1. + active_pane_flex;
-            } else {
-                for flex in self.flexes.borrow().iter() {
-                    remaining_flex += flex;
-                }
-            }
-
-            let mut cross_axis_max: f32 = 0.0;
-            let mut remaining_space = constraint.max_along(self.axis);
-
-            if remaining_space.is_infinite() {
-                panic!("flex contains flexible children but has an infinite constraint along the flex axis");
-            }
+    impl IntoElement for PaneAxisElement {
+        type Element = Self;
 
-            self.layout_children(
-                active_pane_magnification,
-                constraint,
-                &mut remaining_space,
-                &mut remaining_flex,
-                &mut cross_axis_max,
-                view,
-                cx,
-            );
-
-            let mut size = match self.axis {
-                Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max),
-                Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space),
-            };
+        fn element_id(&self) -> Option<ui::prelude::ElementId> {
+            Some(self.basis.into())
+        }
 
-            if constraint.min.x().is_finite() {
-                size.set_x(size.x().max(constraint.min.x()));
-            }
-            if constraint.min.y().is_finite() {
-                size.set_y(size.y().max(constraint.min.y()));
-            }
+        fn into_element(self) -> Self::Element {
+            self
+        }
+    }
 
-            if size.x() > constraint.max.x() {
-                size.set_x(constraint.max.x());
-            }
-            if size.y() > constraint.max.y() {
-                size.set_y(constraint.max.y());
-            }
+    impl Element for PaneAxisElement {
+        type State = Rc<RefCell<Option<usize>>>;
 
-            (size, remaining_space)
+        fn request_layout(
+            &mut self,
+            state: Option<Self::State>,
+            cx: &mut ui::prelude::WindowContext,
+        ) -> (gpui::LayoutId, Self::State) {
+            let mut style = Style::default();
+            style.flex_grow = 1.;
+            style.flex_shrink = 1.;
+            style.flex_basis = relative(0.).into();
+            style.size.width = relative(1.).into();
+            style.size.height = relative(1.).into();
+            let layout_id = cx.request_layout(&style, None);
+            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
+            (layout_id, dragged_pane)
         }
 
         fn paint(
             &mut self,
-            bounds: RectF,
-            visible_bounds: RectF,
-            remaining_space: &mut Self::LayoutState,
-            view: &mut Workspace,
-            cx: &mut ViewContext<Workspace>,
-        ) -> Self::PaintState {
-            let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
-            let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-            let overflowing = *remaining_space < 0.;
-            if overflowing {
-                cx.scene().push_layer(Some(visible_bounds));
-            }
+            bounds: gpui::Bounds<ui::prelude::Pixels>,
+            state: &mut Self::State,
+            cx: &mut ui::prelude::WindowContext,
+        ) {
+            let flexes = self.flexes.lock().clone();
+            let len = self.children.len();
+            debug_assert!(flexes.len() == len);
+            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
 
-            let mut child_origin = bounds.origin();
+            let mut origin = bounds.origin;
+            let space_per_flex = bounds.size.along(self.axis) / len as f32;
 
-            let mut bounding_boxes = self.bounding_boxes.borrow_mut();
+            let mut bounding_boxes = self.bounding_boxes.lock();
             bounding_boxes.clear();
 
-            let mut children_iter = self.children.iter_mut().enumerate().peekable();
-            while let Some((ix, child)) = children_iter.next() {
-                let child_start = child_origin.clone();
-                child.paint(child_origin, visible_bounds, view, cx);
-
-                bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
-
-                match self.axis {
-                    Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
-                    Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
-                }
-
-                if can_resize && children_iter.peek().is_some() {
-                    cx.scene().push_stacking_context(None, None);
-
-                    let handle_origin = match self.axis {
-                        Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
-                        Axis::Vertical => child_origin - vec2f(0.0, HANDLE_HITBOX_SIZE / 2.),
-                    };
-
-                    let handle_bounds = match self.axis {
-                        Axis::Horizontal => RectF::new(
-                            handle_origin,
-                            vec2f(HANDLE_HITBOX_SIZE, visible_bounds.height()),
-                        ),
-                        Axis::Vertical => RectF::new(
-                            handle_origin,
-                            vec2f(visible_bounds.width(), HANDLE_HITBOX_SIZE),
-                        ),
-                    };
-
-                    let style = match self.axis {
-                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
-                        Axis::Vertical => CursorStyle::ResizeUpDown,
-                    };
-
-                    cx.scene().push_cursor_region(CursorRegion {
-                        bounds: handle_bounds,
-                        style,
-                    });
-
-                    enum ResizeHandle {}
-                    let mut mouse_region = MouseRegion::new::<ResizeHandle>(
-                        cx.view_id(),
-                        self.basis + ix,
-                        handle_bounds,
-                    );
-                    mouse_region = mouse_region
-                        .on_drag(
-                            MouseButton::Left,
-                            Self::handle_resize(
-                                self.flexes.clone(),
-                                self.axis,
-                                ix,
-                                child_start,
-                                visible_bounds.clone(),
-                            ),
-                        )
-                        .on_click(MouseButton::Left, {
-                            let flexes = self.flexes.clone();
-                            move |e, v: &mut Workspace, cx| {
-                                if e.click_count >= 2 {
-                                    let mut borrow = flexes.borrow_mut();
-                                    *borrow = vec![1.; borrow.len()];
-                                    v.schedule_serialize(cx);
-                                    cx.notify();
-                                }
-                            }
-                        });
-                    cx.scene().push_mouse_region(mouse_region);
+            for (ix, child) in self.children.iter_mut().enumerate() {
+                //todo!(active_pane_magnification)
+                // If using active pane magnification, need to switch to using
+                // 1 for all non-active panes, and then the magnification for the
+                // active pane.
+                let child_size = bounds
+                    .size
+                    .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
+
+                let child_bounds = Bounds {
+                    origin,
+                    size: child_size,
+                };
+                bounding_boxes.push(Some(child_bounds));
+                cx.with_z_index(0, |cx| {
+                    child.draw(origin, child_size.into(), cx);
+                });
+                cx.with_z_index(1, |cx| {
+                    if ix < len - 1 {
+                        Self::push_handle(
+                            self.flexes.clone(),
+                            state.clone(),
+                            self.axis,
+                            ix,
+                            child_bounds,
+                            bounds,
+                            cx,
+                        );
+                    }
+                });
 
-                    cx.scene().pop_stacking_context();
-                }
+                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
             }
 
-            if overflowing {
-                cx.scene().pop_layer();
-            }
+            cx.with_z_index(1, |cx| {
+                cx.on_mouse_event({
+                    let state = state.clone();
+                    move |_: &MouseUpEvent, phase, _cx| {
+                        if phase.bubble() {
+                            state.replace(None);
+                        }
+                    }
+                });
+            })
         }
+    }
 
-        fn rect_for_text_range(
-            &self,
-            range_utf16: Range<usize>,
-            _: RectF,
-            _: RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            view: &Workspace,
-            cx: &ViewContext<Workspace>,
-        ) -> Option<RectF> {
-            self.children
-                .iter()
-                .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
+    impl ParentElement for PaneAxisElement {
+        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
+            &mut self.children
         }
+    }
 
-        fn debug(
-            &self,
-            bounds: RectF,
-            _: &Self::LayoutState,
-            _: &Self::PaintState,
-            view: &Workspace,
-            cx: &ViewContext<Workspace>,
-        ) -> json::Value {
-            serde_json::json!({
-                "type": "PaneAxis",
-                "bounds": bounds.to_json(),
-                "axis": self.axis.to_json(),
-                "flexes": *self.flexes.borrow(),
-                "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<json::Value>>()
-            })
-        }
+    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
+        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
     }
 }

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

@@ -1,4 +1,4 @@
-#![allow(dead_code)]
+//#![allow(dead_code)]
 
 pub mod model;
 
@@ -6,7 +6,7 @@ use std::path::Path;
 
 use anyhow::{anyhow, bail, Context, Result};
 use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
-use gpui::{platform::WindowBounds, Axis};
+use gpui::{Axis, WindowBounds};
 
 use util::{unzip_option, ResultExt};
 use uuid::Uuid;
@@ -403,7 +403,7 @@ impl WorkspaceDb {
         .map(|(group_id, axis, pane_id, active, flexes)| {
             if let Some((group_id, axis)) = group_id.zip(axis) {
                 let flexes = flexes
-                    .map(|flexes| serde_json::from_str::<Vec<f32>>(&flexes))
+                    .map(|flexes: String| serde_json::from_str::<Vec<f32>>(&flexes))
                     .transpose()?;
 
                 Ok(SerializedPaneGroup::Group {
@@ -553,6 +553,7 @@ impl WorkspaceDb {
 mod tests {
     use super::*;
     use db::open_test_db;
+    use gpui;
 
     #[gpui::test]
     async fn test_next_id_stability() {
@@ -612,13 +613,13 @@ mod tests {
             conn.migrate(
                 "test_table",
                 &[sql!(
-                    CREATE TABLE test_table(
-                        text TEXT,
-                        workspace_id INTEGER,
-                        FOREIGN KEY(workspace_id)
-                            REFERENCES workspaces(workspace_id)
-                        ON DELETE CASCADE
-                    ) STRICT;)],
+                        CREATE TABLE test_table(
+                            text TEXT,
+                            workspace_id INTEGER,
+                            FOREIGN KEY(workspace_id)
+                                REFERENCES workspaces(workspace_id)
+                            ON DELETE CASCADE
+                        ) STRICT;)],
             )
         })
         .await
@@ -680,7 +681,7 @@ mod tests {
         assert_eq!(test_text_1, "test-text-1");
     }
 
-    fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
+    fn group(axis: Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
         SerializedPaneGroup::Group {
             axis,
             flexes: None,
@@ -700,10 +701,10 @@ mod tests {
         //  | 3,4   |       |
         //  -----------------
         let center_group = group(
-            gpui::Axis::Horizontal,
+            Axis::Horizontal,
             vec![
                 group(
-                    gpui::Axis::Vertical,
+                    Axis::Vertical,
                     vec![
                         SerializedPaneGroup::Pane(SerializedPane::new(
                             vec![
@@ -859,10 +860,10 @@ mod tests {
         //  | 3,4   |       |
         //  -----------------
         let center_pane = group(
-            gpui::Axis::Horizontal,
+            Axis::Horizontal,
             vec![
                 group(
-                    gpui::Axis::Vertical,
+                    Axis::Vertical,
                     vec![
                         SerializedPaneGroup::Pane(SerializedPane::new(
                             vec![
@@ -906,10 +907,10 @@ mod tests {
         let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
 
         let center_pane = group(
-            gpui::Axis::Horizontal,
+            Axis::Horizontal,
             vec![
                 group(
-                    gpui::Axis::Vertical,
+                    Axis::Vertical,
                     vec![
                         SerializedPaneGroup::Pane(SerializedPane::new(
                             vec![
@@ -944,7 +945,7 @@ mod tests {
         db.save_workspace(workspace.clone()).await;
 
         workspace.center_group = group(
-            gpui::Axis::Vertical,
+            Axis::Vertical,
             vec![
                 SerializedPaneGroup::Pane(SerializedPane::new(
                     vec![

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

@@ -5,9 +5,7 @@ use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use gpui::{
-    platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle,
-};
+use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds};
 use project::Project;
 use std::{
     path::{Path, PathBuf},
@@ -151,15 +149,11 @@ impl SerializedPaneGroup {
     #[async_recursion(?Send)]
     pub(crate) async fn deserialize(
         self,
-        project: &ModelHandle<Project>,
+        project: &Model<Project>,
         workspace_id: WorkspaceId,
-        workspace: &WeakViewHandle<Workspace>,
-        cx: &mut AsyncAppContext,
-    ) -> Option<(
-        Member,
-        Option<ViewHandle<Pane>>,
-        Vec<Option<Box<dyn ItemHandle>>>,
-    )> {
+        workspace: WeakView<Workspace>,
+        cx: &mut AsyncWindowContext,
+    ) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
         match self {
             SerializedPaneGroup::Group {
                 axis,
@@ -171,7 +165,7 @@ impl SerializedPaneGroup {
                 let mut items = Vec::new();
                 for child in children {
                     if let Some((new_member, active_pane, new_items)) = child
-                        .deserialize(project, workspace_id, workspace, cx)
+                        .deserialize(project, workspace_id, workspace.clone(), cx)
                         .await
                     {
                         members.push(new_member);
@@ -200,18 +194,15 @@ impl SerializedPaneGroup {
                     .log_err()?;
                 let active = serialized_pane.active;
                 let new_items = serialized_pane
-                    .deserialize_to(project, &pane, workspace_id, workspace, cx)
+                    .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
                     .await
                     .log_err()?;
 
-                if pane
-                    .read_with(cx, |pane, _| pane.items_len() != 0)
-                    .log_err()?
-                {
-                    let pane = pane.upgrade(cx)?;
+                if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
+                    let pane = pane.upgrade()?;
                     Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
                 } else {
-                    let pane = pane.upgrade(cx)?;
+                    let pane = pane.upgrade()?;
                     workspace
                         .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
                         .log_err()?;
@@ -235,11 +226,11 @@ impl SerializedPane {
 
     pub async fn deserialize_to(
         &self,
-        project: &ModelHandle<Project>,
-        pane: &WeakViewHandle<Pane>,
+        project: &Model<Project>,
+        pane: &WeakView<Pane>,
         workspace_id: WorkspaceId,
-        workspace: &WeakViewHandle<Workspace>,
-        cx: &mut AsyncAppContext,
+        workspace: WeakView<Workspace>,
+        cx: &mut AsyncWindowContext,
     ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
         let mut items = Vec::new();
         let mut active_item_index = None;
@@ -284,7 +275,7 @@ impl SerializedPane {
 
 pub type GroupId = i64;
 pub type PaneId = i64;
-pub type ItemId = usize;
+pub type ItemId = u64;
 
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub struct SerializedItem {

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

@@ -1,12 +1,15 @@
 use std::{any::Any, sync::Arc};
 
 use gpui::{
-    AnyViewHandle, AnyWeakViewHandle, AppContext, Subscription, Task, ViewContext, ViewHandle,
-    WeakViewHandle, WindowContext,
+    AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
+    WindowContext,
 };
 use project::search::SearchQuery;
 
-use crate::{item::WeakItemHandle, Item, ItemHandle};
+use crate::{
+    item::{Item, WeakItemHandle},
+    ItemHandle,
+};
 
 #[derive(Debug)]
 pub enum SearchEvent {
@@ -29,7 +32,7 @@ pub struct SearchOptions {
     pub replacement: bool,
 }
 
-pub trait SearchableItem: Item {
+pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
     type Match: Any + Sync + Send + Clone;
 
     fn supported_options() -> SearchOptions {
@@ -40,11 +43,7 @@ pub trait SearchableItem: Item {
             replacement: true,
         }
     }
-    fn to_search_event(
-        &mut self,
-        event: &Self::Event,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<SearchEvent>;
+
     fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
     fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
     fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
@@ -95,7 +94,7 @@ pub trait SearchableItemHandle: ItemHandle {
     fn subscribe_to_search_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
+        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
     ) -> Subscription;
     fn clear_matches(&self, cx: &mut WindowContext);
     fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
@@ -128,7 +127,8 @@ pub trait SearchableItemHandle: ItemHandle {
     ) -> Option<usize>;
 }
 
-impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
+// todo!("here is where we need to use AnyWeakView");
+impl<T: SearchableItem> SearchableItemHandle for View<T> {
     fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
         Box::new(self.downgrade())
     }
@@ -144,14 +144,9 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
     fn subscribe_to_search_events(
         &self,
         cx: &mut WindowContext,
-        handler: Box<dyn Fn(SearchEvent, &mut WindowContext)>,
+        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
     ) -> Subscription {
-        cx.subscribe(self, move |handle, event, cx| {
-            let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx));
-            if let Some(search_event) = search_event {
-                handler(search_event, cx)
-            }
-        })
+        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
     }
 
     fn clear_matches(&self, cx: &mut WindowContext) {
@@ -198,7 +193,7 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
         cx: &mut WindowContext,
     ) -> Task<Vec<Box<dyn Any + Send>>> {
         let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
-        cx.foreground().spawn(async {
+        cx.spawn(|_| async {
             let matches = matches.await;
             matches
                 .into_iter()
@@ -231,21 +226,21 @@ fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T
         )
 }
 
-impl From<Box<dyn SearchableItemHandle>> for AnyViewHandle {
+impl From<Box<dyn SearchableItemHandle>> for AnyView {
     fn from(this: Box<dyn SearchableItemHandle>) -> Self {
-        this.as_any().clone()
+        this.to_any().clone()
     }
 }
 
-impl From<&Box<dyn SearchableItemHandle>> for AnyViewHandle {
+impl From<&Box<dyn SearchableItemHandle>> for AnyView {
     fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
-        this.as_any().clone()
+        this.to_any().clone()
     }
 }
 
 impl PartialEq for Box<dyn SearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {
-        self.id() == other.id() && self.window() == other.window()
+        self.item_id() == other.item_id()
     }
 }
 
@@ -254,22 +249,22 @@ impl Eq for Box<dyn SearchableItemHandle> {}
 pub trait WeakSearchableItemHandle: WeakItemHandle {
     fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
 
-    fn into_any(self) -> AnyWeakViewHandle;
+    // fn into_any(self) -> AnyWeakView;
 }
 
-impl<T: SearchableItem> WeakSearchableItemHandle for WeakViewHandle<T> {
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(self.upgrade(cx)?))
+impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
+    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+        Some(Box::new(self.upgrade()?))
     }
 
-    fn into_any(self) -> AnyWeakViewHandle {
-        self.into_any()
-    }
+    // fn into_any(self) -> AnyView {
+    //     self.into_any()
+    // }
 }
 
 impl PartialEq for Box<dyn WeakSearchableItemHandle> {
     fn eq(&self, other: &Self) -> bool {
-        self.id() == other.id() && self.window() == other.window()
+        self.id() == other.id()
     }
 }
 
@@ -277,6 +272,6 @@ impl Eq for Box<dyn WeakSearchableItemHandle> {}
 
 impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
     fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        (self.id(), self.window().id()).hash(state)
+        self.id().hash(state)
     }
 }

crates/workspace/src/shared_screen.rs πŸ”—

@@ -7,16 +7,12 @@ use call::participant::{Frame, RemoteVideoTrack};
 use client::{proto::PeerId, User};
 use futures::StreamExt;
 use gpui::{
-    elements::*,
-    geometry::{rect::RectF, vector::vec2f},
-    platform::MouseButton,
-    AppContext, Entity, Task, View, ViewContext,
-};
-use smallvec::SmallVec;
-use std::{
-    borrow::Cow,
-    sync::{Arc, Weak},
+    div, img, AppContext, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
+    ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
+    WindowContext,
 };
+use std::sync::{Arc, Weak};
+use ui::{h_stack, prelude::*, Icon, IconElement, Label};
 
 pub enum Event {
     Close,
@@ -29,6 +25,7 @@ pub struct SharedScreen {
     user: Arc<User>,
     nav_history: Option<ItemNavHistory>,
     _maintain_frame: Task<Result<()>>,
+    focus: FocusHandle,
 }
 
 impl SharedScreen {
@@ -38,6 +35,7 @@ impl SharedScreen {
         user: Arc<User>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
+        cx.focus_handle();
         let mut frames = track.frames();
         Self {
             track: Arc::downgrade(track),
@@ -55,77 +53,56 @@ impl SharedScreen {
                 this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
                 Ok(())
             }),
+            focus: cx.focus_handle(),
         }
     }
 }
 
-impl Entity for SharedScreen {
-    type Event = Event;
-}
+impl EventEmitter<Event> for SharedScreen {}
 
-impl View for SharedScreen {
-    fn ui_name() -> &'static str {
-        "SharedScreen"
+impl FocusableView for SharedScreen {
+    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
+        self.focus.clone()
     }
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        enum Focus {}
-
-        let frame = self.frame.clone();
-        MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
-            Canvas::new(move |bounds, _, _, cx| {
-                if let Some(frame) = frame.clone() {
-                    let size = constrain_size_preserving_aspect_ratio(
-                        bounds.size(),
-                        vec2f(frame.width() as f32, frame.height() as f32),
-                    );
-                    let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
-                    cx.scene().push_surface(gpui::platform::mac::Surface {
-                        bounds: RectF::new(origin, size),
-                        image_buffer: frame.image(),
-                    });
-                }
-            })
-            .contained()
-            .with_style(theme::current(cx).shared_screen)
-        })
-        .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
-        .into_any()
+}
+impl Render for SharedScreen {
+    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
+        div().track_focus(&self.focus).size_full().children(
+            self.frame
+                .as_ref()
+                .map(|frame| img(frame.image()).size_full()),
+        )
     }
 }
 
 impl Item for SharedScreen {
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+    type Event = Event;
+
+    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
         Some(format!("{}'s screen", self.user.github_login).into())
     }
+
     fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
         if let Some(nav_history) = self.nav_history.as_mut() {
             nav_history.push::<()>(None, cx);
         }
     }
 
-    fn tab_content<V: 'static>(
+    fn tab_content(
         &self,
         _: Option<usize>,
-        style: &theme::Tab,
-        _: &AppContext,
-    ) -> gpui::AnyElement<V> {
-        Flex::row()
-            .with_child(
-                Svg::new("icons/desktop.svg")
-                    .with_color(style.label.text.color)
-                    .constrained()
-                    .with_width(style.type_icon_width)
-                    .aligned()
-                    .contained()
-                    .with_margin_right(style.spacing),
-            )
-            .with_child(
-                Label::new(
-                    format!("{}'s screen", self.user.github_login),
-                    style.label.clone(),
-                )
-                .aligned(),
+        selected: bool,
+        _: &WindowContext<'_>,
+    ) -> gpui::AnyElement {
+        h_stack()
+            .gap_1()
+            .child(IconElement::new(Icon::Screen))
+            .child(
+                Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
+                    Color::Default
+                } else {
+                    Color::Muted
+                }),
             )
             .into_any()
     }
@@ -138,14 +115,14 @@ impl Item for SharedScreen {
         &self,
         _workspace_id: WorkspaceId,
         cx: &mut ViewContext<Self>,
-    ) -> Option<Self> {
+    ) -> Option<View<Self>> {
         let track = self.track.upgrade()?;
-        Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
+        Some(cx.new_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
     }
 
-    fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
         match event {
-            Event::Close => smallvec::smallvec!(ItemEvent::CloseItem),
+            Event::Close => f(ItemEvent::CloseItem),
         }
     }
 }

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

@@ -1,18 +1,13 @@
-use std::ops::Range;
-
 use crate::{ItemHandle, Pane};
 use gpui::{
-    elements::*,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    json::{json, ToJson},
-    AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle,
+    div, AnyView, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
     WindowContext,
 };
+use std::any::TypeId;
+use ui::{h_stack, prelude::*};
+use util::ResultExt;
 
-pub trait StatusItemView: View {
+pub trait StatusItemView: Render {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -20,63 +15,57 @@ pub trait StatusItemView: View {
     );
 }
 
-trait StatusItemViewHandle {
-    fn as_any(&self) -> &AnyViewHandle;
+trait StatusItemViewHandle: Send {
+    fn to_any(&self) -> AnyView;
     fn set_active_pane_item(
         &self,
         active_pane_item: Option<&dyn ItemHandle>,
         cx: &mut WindowContext,
     );
-    fn ui_name(&self) -> &'static str;
+    fn item_type(&self) -> TypeId;
 }
 
 pub struct StatusBar {
     left_items: Vec<Box<dyn StatusItemViewHandle>>,
     right_items: Vec<Box<dyn StatusItemViewHandle>>,
-    active_pane: ViewHandle<Pane>,
+    active_pane: View<Pane>,
     _observe_active_pane: Subscription,
 }
 
-impl Entity for StatusBar {
-    type Event = ();
+impl Render for StatusBar {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        div()
+            .py_0p5()
+            .px_1()
+            .flex()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .h_8()
+            .bg(cx.theme().colors().status_bar_background)
+            .child(self.render_left_tools(cx))
+            .child(self.render_right_tools(cx))
+    }
 }
 
-impl View for StatusBar {
-    fn ui_name() -> &'static str {
-        "StatusBar"
+impl StatusBar {
+    fn render_left_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
+        h_stack()
+            .items_center()
+            .gap_2()
+            .children(self.left_items.iter().map(|item| item.to_any()))
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).workspace.status_bar;
-
-        StatusBarElement {
-            left: Flex::row()
-                .with_children(self.left_items.iter().map(|i| {
-                    ChildView::new(i.as_any(), cx)
-                        .aligned()
-                        .contained()
-                        .with_margin_right(theme.item_spacing)
-                }))
-                .into_any(),
-            right: Flex::row()
-                .with_children(self.right_items.iter().rev().map(|i| {
-                    ChildView::new(i.as_any(), cx)
-                        .aligned()
-                        .contained()
-                        .with_margin_left(theme.item_spacing)
-                }))
-                .into_any(),
-        }
-        .contained()
-        .with_style(theme.container)
-        .constrained()
-        .with_height(theme.height)
-        .into_any()
+    fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
+        h_stack()
+            .items_center()
+            .gap_2()
+            .children(self.right_items.iter().rev().map(|item| item.to_any()))
     }
 }
 
 impl StatusBar {
-    pub fn new(active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) -> Self {
+    pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
         let mut this = Self {
             left_items: Default::default(),
             right_items: Default::default(),
@@ -88,19 +77,22 @@ impl StatusBar {
         this
     }
 
-    pub fn add_left_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
     where
         T: 'static + StatusItemView,
     {
+        let active_pane_item = self.active_pane.read(cx).active_item();
+        item.set_active_pane_item(active_pane_item.as_deref(), cx);
+
         self.left_items.push(Box::new(item));
         cx.notify();
     }
 
-    pub fn item_of_type<T: StatusItemView>(&self) -> Option<ViewHandle<T>> {
+    pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
         self.left_items
             .iter()
             .chain(self.right_items.iter())
-            .find_map(|item| item.as_any().clone().downcast())
+            .find_map(|item| item.to_any().clone().downcast().log_err())
     }
 
     pub fn position_of_item<T>(&self) -> Option<usize>
@@ -108,12 +100,12 @@ impl StatusBar {
         T: StatusItemView,
     {
         for (index, item) in self.left_items.iter().enumerate() {
-            if item.as_ref().ui_name() == T::ui_name() {
+            if item.item_type() == TypeId::of::<T>() {
                 return Some(index);
             }
         }
         for (index, item) in self.right_items.iter().enumerate() {
-            if item.as_ref().ui_name() == T::ui_name() {
+            if item.item_type() == TypeId::of::<T>() {
                 return Some(index + self.left_items.len());
             }
         }
@@ -123,11 +115,14 @@ impl StatusBar {
     pub fn insert_item_after<T>(
         &mut self,
         position: usize,
-        item: ViewHandle<T>,
+        item: View<T>,
         cx: &mut ViewContext<Self>,
     ) where
         T: 'static + StatusItemView,
     {
+        let active_pane_item = self.active_pane.read(cx).active_item();
+        item.set_active_pane_item(active_pane_item.as_deref(), cx);
+
         if position < self.left_items.len() {
             self.left_items.insert(position + 1, Box::new(item))
         } else {
@@ -146,15 +141,18 @@ impl StatusBar {
         cx.notify();
     }
 
-    pub fn add_right_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
     where
         T: 'static + StatusItemView,
     {
+        let active_pane_item = self.active_pane.read(cx).active_item();
+        item.set_active_pane_item(active_pane_item.as_deref(), cx);
+
         self.right_items.push(Box::new(item));
         cx.notify();
     }
 
-    pub fn set_active_pane(&mut self, active_pane: &ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
+    pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
         self.active_pane = active_pane.clone();
         self._observe_active_pane =
             cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
@@ -169,9 +167,9 @@ impl StatusBar {
     }
 }
 
-impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
-    fn as_any(&self) -> &AnyViewHandle {
-        self
+impl<T: StatusItemView> StatusItemViewHandle for View<T> {
+    fn to_any(&self) -> AnyView {
+        self.clone().into()
     }
 
     fn set_active_pane_item(
@@ -184,88 +182,13 @@ impl<T: StatusItemView> StatusItemViewHandle for ViewHandle<T> {
         });
     }
 
-    fn ui_name(&self) -> &'static str {
-        T::ui_name()
+    fn item_type(&self) -> TypeId {
+        TypeId::of::<T>()
     }
 }
 
-impl From<&dyn StatusItemViewHandle> for AnyViewHandle {
+impl From<&dyn StatusItemViewHandle> for AnyView {
     fn from(val: &dyn StatusItemViewHandle) -> Self {
-        val.as_any().clone()
-    }
-}
-
-struct StatusBarElement {
-    left: AnyElement<StatusBar>,
-    right: AnyElement<StatusBar>,
-}
-
-impl Element<StatusBar> for StatusBarElement {
-    type LayoutState = ();
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        mut constraint: SizeConstraint,
-        view: &mut StatusBar,
-        cx: &mut ViewContext<StatusBar>,
-    ) -> (Vector2F, Self::LayoutState) {
-        let max_width = constraint.max.x();
-        constraint.min = vec2f(0., constraint.min.y());
-
-        let right_size = self.right.layout(constraint, view, cx);
-        let constraint = SizeConstraint::new(
-            vec2f(0., constraint.min.y()),
-            vec2f(max_width - right_size.x(), constraint.max.y()),
-        );
-
-        self.left.layout(constraint, view, cx);
-
-        (vec2f(max_width, right_size.y()), ())
-    }
-
-    fn paint(
-        &mut self,
-        bounds: RectF,
-        visible_bounds: RectF,
-        _: &mut Self::LayoutState,
-        view: &mut StatusBar,
-        cx: &mut ViewContext<StatusBar>,
-    ) -> Self::PaintState {
-        let origin_y = bounds.upper_right().y();
-        let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-
-        let left_origin = vec2f(bounds.lower_left().x(), origin_y);
-        self.left.paint(left_origin, visible_bounds, view, cx);
-
-        let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
-        self.right.paint(right_origin, visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        _: Range<usize>,
-        _: RectF,
-        _: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &StatusBar,
-        _: &ViewContext<StatusBar>,
-    ) -> Option<RectF> {
-        None
-    }
-
-    fn debug(
-        &self,
-        bounds: RectF,
-        _: &Self::LayoutState,
-        _: &Self::PaintState,
-        _: &StatusBar,
-        _: &ViewContext<StatusBar>,
-    ) -> serde_json::Value {
-        json!({
-            "type": "StatusBarElement",
-            "bounds": bounds.to_json()
-        })
+        val.to_any().clone()
     }
 }

crates/workspace/src/toolbar.rs πŸ”—

@@ -1,38 +1,35 @@
 use crate::ItemHandle;
 use gpui::{
-    elements::*, AnyElement, AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle,
+    AnyView, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View, ViewContext,
     WindowContext,
 };
+use ui::prelude::*;
+use ui::{h_stack, v_stack};
 
-pub trait ToolbarItemView: View {
+pub enum ToolbarItemEvent {
+    ChangeLocation(ToolbarItemLocation),
+}
+
+pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
         cx: &mut ViewContext<Self>,
     ) -> ToolbarItemLocation;
 
-    fn location_for_event(
-        &self,
-        _event: &Self::Event,
-        current_location: ToolbarItemLocation,
-        _cx: &AppContext,
-    ) -> ToolbarItemLocation {
-        current_location
-    }
-
     fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
 
     /// Number of times toolbar's height will be repeated to get the effective height.
     /// Useful when multiple rows one under each other are needed.
     /// The rows have the same width and act as a whole when reacting to resizes and similar events.
-    fn row_count(&self, _cx: &ViewContext<Self>) -> usize {
+    fn row_count(&self, _cx: &WindowContext) -> usize {
         1
     }
 }
 
-trait ToolbarItemViewHandle {
-    fn id(&self) -> usize;
-    fn as_any(&self) -> &AnyViewHandle;
+trait ToolbarItemViewHandle: Send {
+    fn id(&self) -> EntityId;
+    fn to_any(&self) -> AnyView;
     fn set_active_pane_item(
         &self,
         active_pane_item: Option<&dyn ItemHandle>,
@@ -45,8 +42,8 @@ trait ToolbarItemViewHandle {
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub enum ToolbarItemLocation {
     Hidden,
-    PrimaryLeft { flex: Option<(f32, bool)> },
-    PrimaryRight { flex: Option<(f32, bool)> },
+    PrimaryLeft,
+    PrimaryRight,
     Secondary,
 }
 
@@ -57,140 +54,161 @@ pub struct Toolbar {
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
-impl Entity for Toolbar {
-    type Event = ();
-}
-
-impl View for Toolbar {
-    fn ui_name() -> &'static str {
-        "Toolbar"
+impl Toolbar {
+    fn has_any_visible_items(&self) -> bool {
+        self.items
+            .iter()
+            .any(|(_item, location)| *location != ToolbarItemLocation::Hidden)
     }
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &theme::current(cx).workspace.toolbar;
-
-        let mut primary_left_items = Vec::new();
-        let mut primary_right_items = Vec::new();
-        let mut secondary_item = None;
-        let spacing = theme.item_spacing;
-        let mut primary_items_row_count = 1;
-
-        for (item, position) in &self.items {
-            match *position {
-                ToolbarItemLocation::Hidden => {}
-
-                ToolbarItemLocation::PrimaryLeft { flex } => {
-                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-                    let left_item = ChildView::new(item.as_any(), cx).aligned();
-                    if let Some((flex, expanded)) = flex {
-                        primary_left_items.push(left_item.flex(flex, expanded).into_any());
-                    } else {
-                        primary_left_items.push(left_item.into_any());
-                    }
-                }
-
-                ToolbarItemLocation::PrimaryRight { flex } => {
-                    primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-                    let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
-                    if let Some((flex, expanded)) = flex {
-                        primary_right_items.push(right_item.flex(flex, expanded).into_any());
-                    } else {
-                        primary_right_items.push(right_item.into_any());
-                    }
-                }
-
-                ToolbarItemLocation::Secondary => {
-                    secondary_item = Some(
-                        ChildView::new(item.as_any(), cx)
-                            .constrained()
-                            .with_height(theme.height * item.row_count(cx) as f32)
-                            .into_any(),
-                    );
-                }
+    fn left_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
+        self.items.iter().filter_map(|(item, location)| {
+            if *location == ToolbarItemLocation::PrimaryLeft {
+                Some(item.as_ref())
+            } else {
+                None
             }
-        }
+        })
+    }
 
-        let container_style = theme.container;
-        let height = theme.height * primary_items_row_count as f32;
+    fn right_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
+        self.items.iter().filter_map(|(item, location)| {
+            if *location == ToolbarItemLocation::PrimaryRight {
+                Some(item.as_ref())
+            } else {
+                None
+            }
+        })
+    }
 
-        let mut primary_items = Flex::row().with_spacing(spacing);
-        primary_items.extend(primary_left_items);
-        primary_items.extend(primary_right_items);
+    fn secondary_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
+        self.items.iter().filter_map(|(item, location)| {
+            if *location == ToolbarItemLocation::Secondary {
+                Some(item.as_ref())
+            } else {
+                None
+            }
+        })
+    }
+}
 
-        let mut toolbar = Flex::column();
-        if !primary_items.is_empty() {
-            toolbar.add_child(primary_items.constrained().with_height(height));
-        }
-        if let Some(secondary_item) = secondary_item {
-            toolbar.add_child(secondary_item);
+impl Render for Toolbar {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        if !self.has_any_visible_items() {
+            return div();
         }
 
-        if toolbar.is_empty() {
-            toolbar.into_any_named("toolbar")
-        } else {
-            toolbar
-                .contained()
-                .with_style(container_style)
-                .into_any_named("toolbar")
-        }
+        let secondary_item = self.secondary_items().next().map(|item| item.to_any());
+
+        let has_left_items = self.left_items().count() > 0;
+        let has_right_items = self.right_items().count() > 0;
+
+        v_stack()
+            .p_2()
+            .when(has_left_items || has_right_items, |this| this.gap_2())
+            .border_b()
+            .border_color(cx.theme().colors().border_variant)
+            .bg(cx.theme().colors().toolbar_background)
+            .child(
+                h_stack()
+                    .justify_between()
+                    .when(has_left_items, |this| {
+                        this.child(
+                            h_stack()
+                                .flex_1()
+                                .justify_start()
+                                .children(self.left_items().map(|item| item.to_any())),
+                        )
+                    })
+                    .when(has_right_items, |this| {
+                        this.child(
+                            h_stack()
+                                .flex_1()
+                                .justify_end()
+                                .children(self.right_items().map(|item| item.to_any())),
+                        )
+                    }),
+            )
+            .children(secondary_item)
     }
 }
 
-// <<<<<<< HEAD
-// =======
-// #[allow(clippy::too_many_arguments)]
-// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
-//     svg_path: &'static str,
-//     style: theme::Interactive<theme::IconButton>,
-//     nav_button_height: f32,
-//     tooltip_style: TooltipStyle,
-//     enabled: bool,
-//     spacing: f32,
-//     on_click: F,
-//     tooltip_action: A,
-//     action_name: &'static str,
-//     cx: &mut ViewContext<Toolbar>,
-// ) -> AnyElement<Toolbar> {
-//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
-//         let style = if enabled {
-//             style.style_for(state)
+// todo!()
+// impl View for Toolbar {
+//     fn ui_name() -> &'static str {
+//         "Toolbar"
+//     }
+
+//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+//         let theme = &theme::current(cx).workspace.toolbar;
+
+//         let mut primary_left_items = Vec::new();
+//         let mut primary_right_items = Vec::new();
+//         let mut secondary_item = None;
+//         let spacing = theme.item_spacing;
+//         let mut primary_items_row_count = 1;
+
+//         for (item, position) in &self.items {
+//             match *position {
+//                 ToolbarItemLocation::Hidden => {}
+
+//                 ToolbarItemLocation::PrimaryLeft { flex } => {
+//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
+//                     let left_item = ChildView::new(item.as_any(), cx).aligned();
+//                     if let Some((flex, expanded)) = flex {
+//                         primary_left_items.push(left_item.flex(flex, expanded).into_any());
+//                     } else {
+//                         primary_left_items.push(left_item.into_any());
+//                     }
+//                 }
+
+//                 ToolbarItemLocation::PrimaryRight { flex } => {
+//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
+//                     let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
+//                     if let Some((flex, expanded)) = flex {
+//                         primary_right_items.push(right_item.flex(flex, expanded).into_any());
+//                     } else {
+//                         primary_right_items.push(right_item.into_any());
+//                     }
+//                 }
+
+//                 ToolbarItemLocation::Secondary => {
+//                     secondary_item = Some(
+//                         ChildView::new(item.as_any(), cx)
+//                             .constrained()
+//                             .with_height(theme.height * item.row_count(cx) as f32)
+//                             .into_any(),
+//                     );
+//                 }
+//             }
+//         }
+
+//         let container_style = theme.container;
+//         let height = theme.height * primary_items_row_count as f32;
+
+//         let mut primary_items = Flex::row().with_spacing(spacing);
+//         primary_items.extend(primary_left_items);
+//         primary_items.extend(primary_right_items);
+
+//         let mut toolbar = Flex::column();
+//         if !primary_items.is_empty() {
+//             toolbar.add_child(primary_items.constrained().with_height(height));
+//         }
+//         if let Some(secondary_item) = secondary_item {
+//             toolbar.add_child(secondary_item);
+//         }
+
+//         if toolbar.is_empty() {
+//             toolbar.into_any_named("toolbar")
 //         } else {
-//             style.disabled_style()
-//         };
-//         Svg::new(svg_path)
-//             .with_color(style.color)
-//             .constrained()
-//             .with_width(style.icon_width)
-//             .aligned()
-//             .contained()
-//             .with_style(style.container)
-//             .constrained()
-//             .with_width(style.button_width)
-//             .with_height(nav_button_height)
-//             .aligned()
-//             .top()
-//     })
-//     .with_cursor_style(if enabled {
-//         CursorStyle::PointingHand
-//     } else {
-//         CursorStyle::default()
-//     })
-//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
-//         on_click(toolbar, cx)
-//     })
-//     .with_tooltip::<A>(
-//         0,
-//         action_name,
-//         Some(Box::new(tooltip_action)),
-//         tooltip_style,
-//         cx,
-//     )
-//     .contained()
-//     .with_margin_right(spacing)
-//     .into_any_named("nav button")
+//             toolbar
+//                 .contained()
+//                 .with_style(container_style)
+//                 .into_any_named("toolbar")
+//         }
+//     }
 // }
 
-// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
 impl Toolbar {
     pub fn new() -> Self {
         Self {
@@ -206,7 +224,7 @@ impl Toolbar {
         cx.notify();
     }
 
-    pub fn add_item<T>(&mut self, item: ViewHandle<T>, cx: &mut ViewContext<Self>)
+    pub fn add_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
     where
         T: 'static + ToolbarItemView,
     {
@@ -215,12 +233,13 @@ impl Toolbar {
             if let Some((_, current_location)) =
                 this.items.iter_mut().find(|(i, _)| i.id() == item.id())
             {
-                let new_location = item
-                    .read(cx)
-                    .location_for_event(event, *current_location, cx);
-                if new_location != *current_location {
-                    *current_location = new_location;
-                    cx.notify();
+                match event {
+                    ToolbarItemEvent::ChangeLocation(new_location) => {
+                        if new_location != current_location {
+                            *current_location = *new_location;
+                            cx.notify();
+                        }
+                    }
                 }
             }
         })
@@ -252,10 +271,10 @@ impl Toolbar {
         }
     }
 
-    pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<ViewHandle<T>> {
+    pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
         self.items
             .iter()
-            .find_map(|(item, _)| item.as_any().clone().downcast())
+            .find_map(|(item, _)| item.to_any().downcast().ok())
     }
 
     pub fn hidden(&self) -> bool {
@@ -263,13 +282,13 @@ impl Toolbar {
     }
 }
 
-impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
-    fn id(&self) -> usize {
-        self.id()
+impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
+    fn id(&self) -> EntityId {
+        self.entity_id()
     }
 
-    fn as_any(&self) -> &AnyViewHandle {
-        self
+    fn to_any(&self) -> AnyView {
+        self.clone().into()
     }
 
     fn set_active_pane_item(
@@ -290,12 +309,13 @@ impl<T: ToolbarItemView> ToolbarItemViewHandle for ViewHandle<T> {
     }
 
     fn row_count(&self, cx: &WindowContext) -> usize {
-        self.read_with(cx, |this, cx| this.row_count(cx))
+        self.read(cx).row_count(cx)
     }
 }
 
-impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
-    fn from(val: &dyn ToolbarItemViewHandle) -> Self {
-        val.as_any().clone()
-    }
-}
+// todo!()
+// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
+//     fn from(val: &dyn ToolbarItemViewHandle) -> Self {
+//         val.as_any().clone()
+//     }
+// }

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

@@ -1,5 +1,6 @@
 pub mod dock;
 pub mod item;
+mod modal_layer;
 pub mod notifications;
 pub mod pane;
 pub mod pane_group;
@@ -10,111 +11,80 @@ mod status_bar;
 mod toolbar;
 mod workspace_settings;
 
-use anyhow::{anyhow, Context, Result};
+use anyhow::{anyhow, Context as _, Result};
 use call::ActiveCall;
 use client::{
     proto::{self, PeerId},
     Client, Status, TelemetrySettings, TypedEnvelope, UserStore,
 };
 use collections::{hash_map, HashMap, HashSet};
-use drag_and_drop::DragAndDrop;
+use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
 use futures::{
     channel::{mpsc, oneshot},
     future::try_join_all,
-    FutureExt, StreamExt,
+    Future, FutureExt, StreamExt,
 };
 use gpui::{
-    actions,
-    elements::*,
-    geometry::{
-        rect::RectF,
-        vector::{vec2f, Vector2F},
-    },
-    impl_actions,
-    platform::{
-        CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
-        WindowBounds, WindowOptions,
-    },
-    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
-    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
-    WeakViewHandle, WindowContext, WindowHandle,
+    actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyModel, AnyView,
+    AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow,
+    Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle,
+    FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
+    ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
+    Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
+    WindowBounds, WindowContext, WindowHandle, WindowOptions,
 };
-use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
+use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;
 use language::{LanguageRegistry, Rope};
-use node_runtime::NodeRuntime;
-use std::{
-    any::TypeId,
-    borrow::Cow,
-    cmp, env,
-    future::Future,
-    path::{Path, PathBuf},
-    rc::Rc,
-    str,
-    sync::{atomic::AtomicUsize, Arc},
-    time::Duration,
-};
-
-use crate::{
-    notifications::NotificationTracker,
-    persistence::model::{
-        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
-    },
-};
-use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
 use lazy_static::lazy_static;
-use notifications::{
-    simple_message_notification::MessageNotification, NotificationHandle, NotifyResultExt,
-};
+pub use modal_layer::*;
+use node_runtime::NodeRuntime;
+use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
 pub use pane::*;
 pub use pane_group::*;
-use persistence::{model::SerializedItem, DB};
+use persistence::DB;
 pub use persistence::{
-    model::{ItemId, WorkspaceLocation},
+    model::{ItemId, SerializedWorkspace, WorkspaceLocation},
     WorkspaceDb, DB as WORKSPACE_DB,
 };
-use postage::prelude::Stream;
+use postage::stream::Stream;
 use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
 use serde::Deserialize;
+use settings::Settings;
 use shared_screen::SharedScreen;
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
-use theme::{Theme, ThemeSettings};
-pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
+use std::{
+    any::TypeId,
+    borrow::Cow,
+    cmp, env,
+    path::{Path, PathBuf},
+    sync::{atomic::AtomicUsize, Arc},
+    time::Duration,
+};
+use theme::{ActiveTheme, ThemeSettings};
+pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
+pub use ui;
+use ui::Label;
 use util::ResultExt;
-pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
+use uuid::Uuid;
+pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
+
+use crate::persistence::model::{
+    DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
+};
 
 lazy_static! {
-    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
+    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
         .ok()
         .as_deref()
-        .and_then(parse_pixel_position_env_var);
-    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
+        .and_then(parse_pixel_size_env_var);
+    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
         .ok()
         .as_deref()
         .and_then(parse_pixel_position_env_var);
 }
 
-pub trait Modal: View {
-    fn has_focus(&self) -> bool;
-    fn dismiss_on_event(event: &Self::Event) -> bool;
-}
-
-trait ModalHandle {
-    fn as_any(&self) -> &AnyViewHandle;
-    fn has_focus(&self, cx: &WindowContext) -> bool;
-}
-
-impl<T: Modal> ModalHandle for ViewHandle<T> {
-    fn as_any(&self) -> &AnyViewHandle {
-        self
-    }
-
-    fn has_focus(&self, cx: &WindowContext) -> bool {
-        self.read(cx).has_focus()
-    }
-}
-
 #[derive(Clone, PartialEq)]
 pub struct RemoveWorktreeFromProject(pub WorktreeId);
 
@@ -183,6 +153,20 @@ pub struct CloseAllItemsAndPanes {
     pub save_intent: Option<SaveIntent>,
 }
 
+impl_actions!(
+    workspace,
+    [
+        ActivatePane,
+        ActivatePaneInDirection,
+        CloseAllItemsAndPanes,
+        NewFileInDirection,
+        OpenTerminal,
+        Save,
+        SaveAll,
+        SwapPaneInDirection,
+    ]
+);
+
 #[derive(Deserialize)]
 pub struct Toast {
     id: usize,
@@ -228,243 +212,126 @@ impl Clone for Toast {
     }
 }
 
-#[derive(Clone, Deserialize, PartialEq)]
+#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
 pub struct OpenTerminal {
     pub working_directory: PathBuf,
 }
 
-impl_actions!(
-    workspace,
-    [
-        ActivatePane,
-        ActivatePaneInDirection,
-        SwapPaneInDirection,
-        NewFileInDirection,
-        Toast,
-        OpenTerminal,
-        SaveAll,
-        Save,
-        CloseAllItemsAndPanes,
-    ]
-);
-
 pub type WorkspaceId = i64;
 
 pub fn init_settings(cx: &mut AppContext) {
-    settings::register::<WorkspaceSettings>(cx);
-    settings::register::<item::ItemSettings>(cx);
+    WorkspaceSettings::register(cx);
+    ItemSettings::register(cx);
 }
 
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     init_settings(cx);
-    pane::init(cx);
     notifications::init(cx);
 
-    cx.add_global_action({
+    cx.on_action(Workspace::close_global);
+    cx.on_action(restart);
+
+    cx.on_action({
         let app_state = Arc::downgrade(&app_state);
         move |_: &Open, cx: &mut AppContext| {
-            let mut paths = cx.prompt_for_paths(PathPromptOptions {
+            let paths = cx.prompt_for_paths(PathPromptOptions {
                 files: true,
                 directories: true,
                 multiple: true,
             });
 
             if let Some(app_state) = app_state.upgrade() {
-                cx.spawn(move |mut cx| async move {
-                    if let Some(paths) = paths.recv().await.flatten() {
+                cx.spawn(move |cx| async move {
+                    if let Some(paths) = paths.await.log_err().flatten() {
                         cx.update(|cx| {
                             open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
-                        });
+                        })
+                        .ok();
                     }
                 })
                 .detach();
             }
         }
     });
-    cx.add_async_action(Workspace::open);
-
-    cx.add_async_action(Workspace::follow_next_collaborator);
-    cx.add_async_action(Workspace::close);
-    cx.add_async_action(Workspace::close_inactive_items_and_panes);
-    cx.add_async_action(Workspace::close_all_items_and_panes);
-    cx.add_global_action(Workspace::close_global);
-    cx.add_global_action(restart);
-    cx.add_async_action(Workspace::save_all);
-    cx.add_action(Workspace::add_folder_to_project);
-
-    cx.add_action(
-        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
-            let pane = workspace.active_pane().clone();
-            workspace.unfollow(&pane, cx);
-        },
-    );
-    cx.add_action(
-        |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
-            workspace
-                .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
-                .detach_and_log_err(cx);
-        },
-    );
-    cx.add_action(
-        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
-            workspace
-                .save_active_item(SaveIntent::SaveAs, cx)
-                .detach_and_log_err(cx);
-        },
-    );
-    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
-        workspace.activate_previous_pane(cx)
-    });
-    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
-        workspace.activate_next_pane(cx)
-    });
-
-    cx.add_action(
-        |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
-            workspace.activate_pane_in_direction(action.0, cx)
-        },
-    );
-
-    cx.add_action(
-        |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
-            workspace.swap_pane_in_direction(action.0, cx)
-        },
-    );
-
-    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
-        workspace.toggle_dock(DockPosition::Left, cx);
-    });
-    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
-        workspace.toggle_dock(DockPosition::Right, cx);
-    });
-    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
-        workspace.toggle_dock(DockPosition::Bottom, cx);
-    });
-    cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
-        workspace.close_all_docks(cx);
-    });
-    cx.add_action(Workspace::activate_pane_at_index);
-    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
-        workspace.reopen_closed_item(cx).detach();
-    });
-    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
-        workspace
-            .go_back(workspace.active_pane().downgrade(), cx)
-            .detach();
-    });
-    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
-        workspace
-            .go_forward(workspace.active_pane().downgrade(), cx)
-            .detach();
-    });
-
-    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
-        cx.spawn(|workspace, mut cx| async move {
-            let err = install_cli::install_cli(&cx)
-                .await
-                .context("Failed to create CLI symlink");
-
-            workspace.update(&mut cx, |workspace, cx| {
-                if matches!(err, Err(_)) {
-                    err.notify_err(workspace, cx);
-                } else {
-                    workspace.show_notification(1, cx, |cx| {
-                        cx.add_view(|_| {
-                            MessageNotification::new("Successfully installed the `zed` binary")
-                        })
-                    });
-                }
-            })
-        })
-        .detach();
-    });
 }
 
-type ProjectItemBuilders = HashMap<
-    TypeId,
-    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
->;
+type ProjectItemBuilders =
+    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
 pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
-    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
-        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
-            let item = model.downcast::<I::Item>().unwrap();
-            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
-        });
+    let builders = cx.default_global::<ProjectItemBuilders>();
+    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
+        let item = model.downcast::<I::Item>().unwrap();
+        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
     });
 }
 
 type FollowableItemBuilder = fn(
-    ViewHandle<Pane>,
-    ViewHandle<Workspace>,
+    View<Pane>,
+    View<Workspace>,
     ViewId,
     &mut Option<proto::view::Variant>,
-    &mut AppContext,
+    &mut WindowContext,
 ) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 type FollowableItemBuilders = HashMap<
     TypeId,
     (
         FollowableItemBuilder,
-        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
+        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
     ),
 >;
 pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
-    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
-        builders.insert(
-            TypeId::of::<I>(),
-            (
-                |pane, workspace, id, state, cx| {
-                    I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
-                        cx.foreground()
-                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
-                    })
-                },
-                |this| Box::new(this.clone().downcast::<I>().unwrap()),
-            ),
-        );
-    });
+    let builders = cx.default_global::<FollowableItemBuilders>();
+    builders.insert(
+        TypeId::of::<I>(),
+        (
+            |pane, workspace, id, state, cx| {
+                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
+                    cx.foreground_executor()
+                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
+                })
+            },
+            |this| Box::new(this.clone().downcast::<I>().unwrap()),
+        ),
+    );
 }
 
 type ItemDeserializers = HashMap<
     Arc<str>,
     fn(
-        ModelHandle<Project>,
-        WeakViewHandle<Workspace>,
+        Model<Project>,
+        WeakView<Workspace>,
         WorkspaceId,
         ItemId,
         &mut ViewContext<Pane>,
     ) -> Task<Result<Box<dyn ItemHandle>>>,
 >;
 pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
-    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
-        if let Some(serialized_item_kind) = I::serialized_item_kind() {
-            deserializers.insert(
-                Arc::from(serialized_item_kind),
-                |project, workspace, workspace_id, item_id, cx| {
-                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
-                    cx.foreground()
-                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
-                },
-            );
-        }
-    });
+    if let Some(serialized_item_kind) = I::serialized_item_kind() {
+        let deserializers = cx.default_global::<ItemDeserializers>();
+        deserializers.insert(
+            Arc::from(serialized_item_kind),
+            |project, workspace, workspace_id, item_id, cx| {
+                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
+                cx.foreground_executor()
+                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
+            },
+        );
+    }
 }
 
 pub struct AppState {
     pub languages: Arc<LanguageRegistry>,
     pub client: Arc<Client>,
-    pub user_store: ModelHandle<UserStore>,
-    pub workspace_store: ModelHandle<WorkspaceStore>,
+    pub user_store: Model<UserStore>,
+    pub workspace_store: Model<WorkspaceStore>,
     pub fs: Arc<dyn fs::Fs>,
     pub build_window_options:
-        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
-    pub initialize_workspace:
-        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
-    pub background_actions: BackgroundActions,
+        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
     pub node_runtime: Arc<dyn NodeRuntime>,
 }
 
 pub struct WorkspaceStore {
-    workspaces: HashSet<WeakViewHandle<Workspace>>,
+    workspaces: HashSet<WindowHandle<Workspace>>,
     followers: Vec<Follower>,
     client: Arc<Client>,
     _subscriptions: Vec<client::Subscription>,
@@ -483,17 +350,18 @@ impl AppState {
         use settings::SettingsStore;
 
         if !cx.has_global::<SettingsStore>() {
-            cx.set_global(SettingsStore::test(cx));
+            let settings_store = SettingsStore::test(cx);
+            cx.set_global(settings_store);
         }
 
-        let fs = fs::FakeFs::new(cx.background().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.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-        let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+        let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
+        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
-        theme::init((), cx);
+        theme::init(theme::LoadThemes::JustBase, cx);
         client::init(&client, cx);
         crate::init_settings(cx);
 
@@ -502,12 +370,9 @@ impl AppState {
             fs,
             languages,
             user_store,
-            // channel_store,
             workspace_store,
             node_runtime: FakeNodeRuntime::new(),
-            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
             build_window_options: |_, _, _| Default::default(),
-            background_actions: || &[],
         })
     }
 }
@@ -527,7 +392,7 @@ impl DelayedDebouncedEditAction {
 
     fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
     where
-        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
+        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
     {
         if let Some(channel) = self.cancel_channel.take() {
             _ = channel.send(());
@@ -537,8 +402,8 @@ impl DelayedDebouncedEditAction {
         self.cancel_channel = Some(sender);
 
         let previous_task = self.task.take();
-        self.task = Some(cx.spawn(|workspace, mut cx| async move {
-            let mut timer = cx.background().timer(delay).fuse();
+        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
+            let mut timer = cx.background_executor().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
             }
@@ -559,46 +424,46 @@ impl DelayedDebouncedEditAction {
 }
 
 pub enum Event {
-    PaneAdded(ViewHandle<Pane>),
+    PaneAdded(View<Pane>),
     ContactRequestedJoin(u64),
+    WorkspaceCreated(WeakView<Workspace>),
 }
 
 pub struct Workspace {
-    weak_self: WeakViewHandle<Self>,
-    modal: Option<ActiveModal>,
-    zoomed: Option<AnyWeakViewHandle>,
+    weak_self: WeakView<Self>,
+    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
+    zoomed: Option<AnyWeakView>,
     zoomed_position: Option<DockPosition>,
     center: PaneGroup,
-    left_dock: ViewHandle<Dock>,
-    bottom_dock: ViewHandle<Dock>,
-    right_dock: ViewHandle<Dock>,
-    panes: Vec<ViewHandle<Pane>>,
-    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
-    active_pane: ViewHandle<Pane>,
-    last_active_center_pane: Option<WeakViewHandle<Pane>>,
+    left_dock: View<Dock>,
+    bottom_dock: View<Dock>,
+    right_dock: View<Dock>,
+    panes: Vec<View<Pane>>,
+    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
+    active_pane: View<Pane>,
+    last_active_center_pane: Option<WeakView<Pane>>,
     last_active_view_id: Option<proto::ViewId>,
-    status_bar: ViewHandle<StatusBar>,
-    titlebar_item: Option<AnyViewHandle>,
+    status_bar: View<StatusBar>,
+    modal_layer: View<ModalLayer>,
+    titlebar_item: Option<AnyView>,
     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
-    project: ModelHandle<Project>,
-    follower_states: HashMap<ViewHandle<Pane>, FollowerState>,
-    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
+    project: Model<Project>,
+    follower_states: HashMap<View<Pane>, FollowerState>,
+    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
     window_edited: bool,
-    active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
+    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
     leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
     database_id: WorkspaceId,
     app_state: Arc<AppState>,
-    subscriptions: Vec<Subscription>,
+    _subscriptions: Vec<Subscription>,
     _apply_leader_updates: Task<Result<()>>,
     _observe_current_user: Task<Result<()>>,
     _schedule_serialize: Option<Task<()>>,
     pane_history_timestamp: Arc<AtomicUsize>,
+    bounds: Bounds<Pixels>,
 }
 
-struct ActiveModal {
-    view: Box<dyn ModalHandle>,
-    previously_focused_view_id: Option<usize>,
-}
+impl EventEmitter<Event> for Workspace {}
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 pub struct ViewId {
@@ -613,12 +478,10 @@ struct FollowerState {
     items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 }
 
-enum WorkspaceBounds {}
-
 impl Workspace {
     pub fn new(
         workspace_id: WorkspaceId,
-        project: ModelHandle<Project>,
+        project: Model<Project>,
         app_state: Arc<AppState>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
@@ -640,7 +503,7 @@ impl Workspace {
 
                 project::Event::DisconnectedFromHost => {
                     this.update_window_edited(cx);
-                    cx.blur();
+                    cx.disable_focus();
                 }
 
                 project::Event::Closed => {
@@ -656,7 +519,7 @@ impl Workspace {
                 }
 
                 project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
-                    cx.add_view(|_| MessageNotification::new(message.clone()))
+                    cx.new_view(|_| MessageNotification::new(message.clone()))
                 }),
 
                 _ => {}
@@ -665,31 +528,39 @@ impl Workspace {
         })
         .detach();
 
-        let weak_handle = cx.weak_handle();
+        cx.on_blur_window(|this, cx| {
+            let focus_handle = this.focus_handle(cx);
+            cx.focus(&focus_handle);
+        })
+        .detach();
+
+        let weak_handle = cx.view().downgrade();
         let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 
-        let center_pane = cx.add_view(|cx| {
+        let center_pane = cx.new_view(|cx| {
             Pane::new(
                 weak_handle.clone(),
                 project.clone(),
-                app_state.background_actions,
                 pane_history_timestamp.clone(),
+                None,
                 cx,
             )
         });
         cx.subscribe(&center_pane, Self::handle_pane_event).detach();
-        cx.focus(&center_pane);
+
+        cx.focus_view(&center_pane);
         cx.emit(Event::PaneAdded(center_pane.clone()));
 
+        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
         app_state.workspace_store.update(cx, |store, _| {
-            store.workspaces.insert(weak_handle.clone());
+            store.workspaces.insert(window_handle);
         });
 
         let mut current_user = app_state.user_store.read(cx).watch_current_user();
         let mut connection_status = app_state.client.status();
         let _observe_current_user = cx.spawn(|this, mut cx| async move {
-            current_user.recv().await;
-            connection_status.recv().await;
+            current_user.next().await;
+            connection_status.next().await;
             let mut stream =
                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 
@@ -713,18 +584,15 @@ impl Workspace {
             Ok(())
         });
 
-        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
-
-        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
-        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
-        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
-        let left_dock_buttons =
-            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
-        let bottom_dock_buttons =
-            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
-        let right_dock_buttons =
-            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
-        let status_bar = cx.add_view(|cx| {
+        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
+
+        let left_dock = Dock::new(DockPosition::Left, cx);
+        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
+        let right_dock = Dock::new(DockPosition::Right, cx);
+        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
+        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
+        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
+        let status_bar = cx.new_view(|cx| {
             let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
             status_bar.add_left_item(left_dock_buttons, cx);
             status_bar.add_right_item(right_dock_buttons, cx);
@@ -732,37 +600,35 @@ impl Workspace {
             status_bar
         });
 
-        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
-            drag_and_drop.register_container(weak_handle.clone());
-        });
+        let modal_layer = cx.new_view(|_| ModalLayer::new());
 
         let mut active_call = None;
-        if cx.has_global::<ModelHandle<ActiveCall>>() {
-            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
+        if cx.has_global::<Model<ActiveCall>>() {
+            let call = cx.global::<Model<ActiveCall>>().clone();
             let mut subscriptions = Vec::new();
             subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
             active_call = Some((call, subscriptions));
         }
 
         let subscriptions = vec![
-            cx.observe_fullscreen(|_, _, cx| cx.notify()),
             cx.observe_window_activation(Self::on_window_activation_changed),
-            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
-                // Transform fixed bounds to be stored in terms of the containing display
-                if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                    if let Some(screen) = cx.platform().screen_by_id(display) {
-                        let screen_bounds = screen.bounds();
-                        window_bounds
-                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
-                        window_bounds
-                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
-                        bounds = WindowBounds::Fixed(window_bounds);
+            cx.observe_window_bounds(move |_, cx| {
+                if let Some(display) = cx.display() {
+                    // Transform fixed bounds to be stored in terms of the containing display
+                    let mut bounds = cx.window_bounds();
+                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
+                        let display_bounds = display.bounds();
+                        window_bounds.origin.x -= display_bounds.origin.x;
+                        window_bounds.origin.y -= display_bounds.origin.y;
                     }
-                }
 
-                cx.background()
-                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
-                    .detach_and_log_err(cx);
+                    if let Some(display_uuid) = display.uuid().log_err() {
+                        cx.background_executor()
+                            .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
+                            .detach_and_log_err(cx);
+                    }
+                }
+                cx.notify();
             }),
             cx.observe(&left_dock, |this, _, cx| {
                 this.serialize_workspace(cx);
@@ -776,14 +642,31 @@ impl Workspace {
                 this.serialize_workspace(cx);
                 cx.notify();
             }),
+            cx.on_release(|this, window, cx| {
+                this.app_state.workspace_store.update(cx, |store, _| {
+                    let window = window.downcast::<Self>().unwrap();
+                    debug_assert!(store.workspaces.remove(&window));
+                })
+            }),
         ];
 
         cx.defer(|this, cx| {
             this.update_window_title(cx);
+            // todo! @nate - these are useful for testing notifications
+            // this.show_error(
+            //     &anyhow::anyhow!("what happens if this message is very very very very very long"),
+            //     cx,
+            // );
+
+            // this.show_notification(1, cx, |cx| {
+            //     cx.build_view(|_cx| {
+            //         simple_message_notification::MessageNotification::new(format!("Error:"))
+            //             .with_click_message("click here because!")
+            //     })
+            // });
         });
         Workspace {
             weak_self: weak_handle.clone(),
-            modal: None,
             zoomed: None,
             zoomed_position: None,
             center: PaneGroup::new(center_pane.clone()),
@@ -793,6 +676,7 @@ impl Workspace {
             last_active_center_pane: Some(center_pane.downgrade()),
             last_active_view_id: None,
             status_bar,
+            modal_layer,
             titlebar_item: None,
             notifications: Default::default(),
             left_dock,
@@ -809,8 +693,11 @@ impl Workspace {
             _apply_leader_updates,
             _schedule_serialize: None,
             leader_updates_tx,
-            subscriptions,
+            _subscriptions: subscriptions,
             pane_history_timestamp,
+            workspace_actions: Default::default(),
+            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
+            bounds: Default::default(),
         }
     }
 
@@ -819,10 +706,12 @@ impl Workspace {
         app_state: Arc<AppState>,
         requesting_window: Option<WindowHandle<Workspace>>,
         cx: &mut AppContext,
-    ) -> Task<(
-        WeakViewHandle<Workspace>,
-        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-    )> {
+    ) -> Task<
+        anyhow::Result<(
+            WindowHandle<Workspace>,
+            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+        )>,
+    > {
         let project_handle = Project::local(
             app_state.client.clone(),
             app_state.node_runtime.clone(),
@@ -833,7 +722,8 @@ impl Workspace {
         );
 
         cx.spawn(|mut cx| async move {
-            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+            let serialized_workspace: Option<SerializedWorkspace> =
+                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 
             let paths_to_open = Arc::new(abs_paths);
 
@@ -845,11 +735,11 @@ impl Workspace {
                 if let Some((worktree, project_entry)) = cx
                     .update(|cx| {
                         Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
-                    })
+                    })?
                     .await
                     .log_err()
                 {
-                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
+                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
                     project_paths.push((path, Some(project_entry)));
                 } else {
                     project_paths.push((path, None));
@@ -863,199 +753,105 @@ impl Workspace {
             };
 
             let window = if let Some(window) = requesting_window {
-                window.replace_root(&mut cx, |cx| {
-                    Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-                });
+                cx.update_window(window.into(), |_, cx| {
+                    cx.replace_root_view(|cx| {
+                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+                    });
+                })?;
                 window
             } else {
-                {
-                    let window_bounds_override = window_bounds_env_override(&cx);
-                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
-                        (Some(bounds), None)
-                    } else {
-                        serialized_workspace
-                            .as_ref()
-                            .and_then(|serialized_workspace| {
-                                let display = serialized_workspace.display?;
-                                let mut bounds = serialized_workspace.bounds?;
-
-                                // Stored bounds are relative to the containing display.
-                                // So convert back to global coordinates if that screen still exists
-                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                    if let Some(screen) = cx.platform().screen_by_id(display) {
-                                        let screen_bounds = screen.bounds();
-                                        window_bounds.set_origin_x(
-                                            window_bounds.origin_x() + screen_bounds.origin_x(),
-                                        );
-                                        window_bounds.set_origin_y(
-                                            window_bounds.origin_y() + screen_bounds.origin_y(),
-                                        );
-                                        bounds = WindowBounds::Fixed(window_bounds);
-                                    } else {
-                                        // Screen no longer exists. Return none here.
-                                        return None;
-                                    }
-                                }
+                let window_bounds_override = window_bounds_env_override(&cx);
+                let (bounds, display) = if let Some(bounds) = window_bounds_override {
+                    (Some(bounds), None)
+                } else {
+                    serialized_workspace
+                        .as_ref()
+                        .and_then(|serialized_workspace| {
+                            let serialized_display = serialized_workspace.display?;
+                            let mut bounds = serialized_workspace.bounds?;
+
+                            // Stored bounds are relative to the containing display.
+                            // So convert back to global coordinates if that screen still exists
+                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
+                                let screen = cx
+                                    .update(|cx| {
+                                        cx.displays().into_iter().find(|display| {
+                                            display.uuid().ok() == Some(serialized_display)
+                                        })
+                                    })
+                                    .ok()??;
+                                let screen_bounds = screen.bounds();
+                                window_bounds.origin.x += screen_bounds.origin.x;
+                                window_bounds.origin.y += screen_bounds.origin.y;
+                                bounds = WindowBounds::Fixed(window_bounds);
+                            }
 
-                                Some((bounds, display))
-                            })
-                            .unzip()
-                    };
+                            Some((bounds, serialized_display))
+                        })
+                        .unzip()
+                };
 
-                    // Use the serialized workspace to construct the new window
-                    cx.add_window(
-                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
-                        |cx| {
-                            Workspace::new(
-                                workspace_id,
-                                project_handle.clone(),
-                                app_state.clone(),
-                                cx,
-                            )
-                        },
-                    )
-                }
+                // Use the serialized workspace to construct the new window
+                let options =
+                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
+
+                cx.open_window(options, {
+                    let app_state = app_state.clone();
+                    let workspace_id = workspace_id.clone();
+                    let project_handle = project_handle.clone();
+                    move |cx| {
+                        cx.new_view(|cx| {
+                            Workspace::new(workspace_id, project_handle, app_state, cx)
+                        })
+                    }
+                })?
             };
 
-            // We haven't yielded the main thread since obtaining the window handle,
-            // so the window exists.
-            let workspace = window.root(&cx).unwrap();
-
-            (app_state.initialize_workspace)(
-                workspace.downgrade(),
-                serialized_workspace.is_some(),
-                app_state.clone(),
-                cx.clone(),
-            )
-            .await
-            .log_err();
-
-            window.update(&mut cx, |cx| cx.activate_window());
+            window
+                .update(&mut cx, |_, cx| cx.activate_window())
+                .log_err();
 
-            let workspace = workspace.downgrade();
-            notify_if_database_failed(&workspace, &mut cx);
-            let opened_items = open_items(
-                serialized_workspace,
-                &workspace,
-                project_paths,
-                app_state,
-                cx,
-            )
-            .await
-            .unwrap_or_default();
+            notify_if_database_failed(window, &mut cx);
+            let opened_items = window
+                .update(&mut cx, |_workspace, cx| {
+                    open_items(serialized_workspace, project_paths, app_state, cx)
+                })?
+                .await
+                .unwrap_or_default();
 
-            (workspace, opened_items)
+            Ok((window, opened_items))
         })
     }
 
-    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
+    pub fn weak_handle(&self) -> WeakView<Self> {
         self.weak_self.clone()
     }
 
-    pub fn left_dock(&self) -> &ViewHandle<Dock> {
+    pub fn left_dock(&self) -> &View<Dock> {
         &self.left_dock
     }
 
-    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
+    pub fn bottom_dock(&self) -> &View<Dock> {
         &self.bottom_dock
     }
 
-    pub fn right_dock(&self) -> &ViewHandle<Dock> {
+    pub fn right_dock(&self) -> &View<Dock> {
         &self.right_dock
     }
 
-    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
-    where
-        T::Event: std::fmt::Debug,
-    {
-        self.add_panel_with_extra_event_handler(panel, cx, |_, _, _, _| {})
-    }
-
-    pub fn add_panel_with_extra_event_handler<T: Panel, F>(
-        &mut self,
-        panel: ViewHandle<T>,
-        cx: &mut ViewContext<Self>,
-        handler: F,
-    ) where
-        T::Event: std::fmt::Debug,
-        F: Fn(&mut Self, &ViewHandle<T>, &T::Event, &mut ViewContext<Self>) + 'static,
-    {
+    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
         let dock = match panel.position(cx) {
             DockPosition::Left => &self.left_dock,
             DockPosition::Bottom => &self.bottom_dock,
             DockPosition::Right => &self.right_dock,
         };
 
-        self.subscriptions.push(cx.subscribe(&panel, {
-            let mut dock = dock.clone();
-            let mut prev_position = panel.position(cx);
-            move |this, panel, event, cx| {
-                if T::should_change_position_on_event(event) {
-                    let new_position = panel.read(cx).position(cx);
-                    let mut was_visible = false;
-                    dock.update(cx, |dock, cx| {
-                        prev_position = new_position;
-
-                        was_visible = dock.is_open()
-                            && dock
-                                .visible_panel()
-                                .map_or(false, |active_panel| active_panel.id() == panel.id());
-                        dock.remove_panel(&panel, cx);
-                    });
-
-                    if panel.is_zoomed(cx) {
-                        this.zoomed_position = Some(new_position);
-                    }
-
-                    dock = match panel.read(cx).position(cx) {
-                        DockPosition::Left => &this.left_dock,
-                        DockPosition::Bottom => &this.bottom_dock,
-                        DockPosition::Right => &this.right_dock,
-                    }
-                    .clone();
-                    dock.update(cx, |dock, cx| {
-                        dock.add_panel(panel.clone(), cx);
-                        if was_visible {
-                            dock.set_open(true, cx);
-                            dock.activate_panel(dock.panels_len() - 1, cx);
-                        }
-                    });
-                } else if T::should_zoom_in_on_event(event) {
-                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
-                    if !panel.has_focus(cx) {
-                        cx.focus(&panel);
-                    }
-                    this.zoomed = Some(panel.downgrade().into_any());
-                    this.zoomed_position = Some(panel.read(cx).position(cx));
-                } else if T::should_zoom_out_on_event(event) {
-                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
-                    if this.zoomed_position == Some(prev_position) {
-                        this.zoomed = None;
-                        this.zoomed_position = None;
-                    }
-                    cx.notify();
-                } else if T::is_focus_event(event) {
-                    let position = panel.read(cx).position(cx);
-                    this.dismiss_zoomed_items_to_reveal(Some(position), cx);
-                    if panel.is_zoomed(cx) {
-                        this.zoomed = Some(panel.downgrade().into_any());
-                        this.zoomed_position = Some(position);
-                    } else {
-                        this.zoomed = None;
-                        this.zoomed_position = None;
-                    }
-                    this.update_active_view_for_followers(cx);
-                    cx.notify();
-                } else {
-                    handler(this, &panel, event, cx)
-                }
-            }
-        }));
-
-        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
+        dock.update(cx, |dock, cx| {
+            dock.add_panel(panel, self.weak_self.clone(), cx)
+        });
     }
 
-    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
+    pub fn status_bar(&self) -> &View<StatusBar> {
         &self.status_bar
     }
 

crates/workspace/src/workspace_settings.rs πŸ”—

@@ -1,6 +1,6 @@
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Setting;
+use settings::Settings;
 
 #[derive(Deserialize)]
 pub struct WorkspaceSettings {
@@ -41,7 +41,7 @@ pub enum GitGutterSetting {
     Hide,
 }
 
-impl Setting for WorkspaceSettings {
+impl Settings for WorkspaceSettings {
     const KEY: Option<&'static str> = None;
 
     type FileContent = WorkspaceSettingsContent;
@@ -49,7 +49,7 @@ impl Setting for WorkspaceSettings {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        _: &gpui::AppContext,
+        _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }

crates/workspace2/Cargo.toml πŸ”—

@@ -1,66 +0,0 @@
-[package]
-name = "workspace2"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-path = "src/workspace2.rs"
-doctest = false
-
-[features]
-test-support = [
-    "call/test-support",
-    "client/test-support",
-    "project/test-support",
-    "settings/test-support",
-    "gpui/test-support",
-    "fs/test-support"
-]
-
-[dependencies]
-db = { path = "../db2", package = "db2" }
-call = { path = "../call2", package = "call2" }
-client = { path = "../client2", package = "client2" }
-collections = { path = "../collections" }
-# context_menu = { path = "../context_menu" }
-fs = { path = "../fs2", package = "fs2" }
-gpui = { package = "gpui2", path = "../gpui2" }
-install_cli = { path = "../install_cli2", package = "install_cli2" }
-language = { path = "../language2", package = "language2" }
-#menu = { path = "../menu" }
-node_runtime = { path = "../node_runtime" }
-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" }
-
-async-recursion = "1.0.0"
-itertools = "0.10"
-bincode = "1.2.1"
-anyhow.workspace = true
-futures.workspace = true
-lazy_static.workspace = true
-log.workspace = true
-parking_lot.workspace = true
-postage.workspace = true
-schemars.workspace = true
-serde.workspace = true
-serde_derive.workspace = true
-serde_json.workspace = true
-smallvec.workspace = true
-uuid.workspace = true
-
-[dev-dependencies]
-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/dock.rs πŸ”—

@@ -1,783 +0,0 @@
-use crate::DraggedDock;
-use crate::{status_bar::StatusItemView, Workspace};
-use gpui::{
-    div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Entity, EntityId,
-    EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render,
-    SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
-};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use std::sync::Arc;
-use ui::{h_stack, ContextMenu, IconButton, Tooltip};
-use ui::{prelude::*, right_click_menu};
-
-const RESIZE_HANDLE_SIZE: Pixels = Pixels(6.);
-
-pub enum PanelEvent {
-    ChangePosition,
-    ZoomIn,
-    ZoomOut,
-    Activate,
-    Close,
-    Focus,
-}
-
-pub trait Panel: FocusableView + EventEmitter<PanelEvent> {
-    fn persistent_name() -> &'static str;
-    fn position(&self, cx: &WindowContext) -> DockPosition;
-    fn position_is_valid(&self, position: DockPosition) -> bool;
-    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
-    fn size(&self, cx: &WindowContext) -> Pixels;
-    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>);
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
-    fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
-    fn toggle_action(&self) -> Box<dyn Action>;
-    fn icon_label(&self, _: &WindowContext) -> Option<String> {
-        None
-    }
-    fn is_zoomed(&self, _cx: &WindowContext) -> bool {
-        false
-    }
-    fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
-    fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
-}
-
-pub trait PanelHandle: Send + Sync {
-    fn panel_id(&self) -> EntityId;
-    fn persistent_name(&self) -> &'static str;
-    fn position(&self, cx: &WindowContext) -> DockPosition;
-    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
-    fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
-    fn is_zoomed(&self, cx: &WindowContext) -> bool;
-    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
-    fn set_active(&self, active: bool, cx: &mut WindowContext);
-    fn size(&self, cx: &WindowContext) -> Pixels;
-    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext);
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon>;
-    fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str>;
-    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action>;
-    fn icon_label(&self, cx: &WindowContext) -> Option<String>;
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
-    fn to_any(&self) -> AnyView;
-}
-
-impl<T> PanelHandle for View<T>
-where
-    T: Panel,
-{
-    fn panel_id(&self) -> EntityId {
-        Entity::entity_id(self)
-    }
-
-    fn persistent_name(&self) -> &'static str {
-        T::persistent_name()
-    }
-
-    fn position(&self, cx: &WindowContext) -> DockPosition {
-        self.read(cx).position(cx)
-    }
-
-    fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
-        self.read(cx).position_is_valid(position)
-    }
-
-    fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.set_position(position, cx))
-    }
-
-    fn is_zoomed(&self, cx: &WindowContext) -> bool {
-        self.read(cx).is_zoomed(cx)
-    }
-
-    fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.set_zoomed(zoomed, cx))
-    }
-
-    fn set_active(&self, active: bool, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.set_active(active, cx))
-    }
-
-    fn size(&self, cx: &WindowContext) -> Pixels {
-        self.read(cx).size(cx)
-    }
-
-    fn set_size(&self, size: Option<Pixels>, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.set_size(size, cx))
-    }
-
-    fn icon(&self, cx: &WindowContext) -> Option<ui::Icon> {
-        self.read(cx).icon(cx)
-    }
-
-    fn icon_tooltip(&self, cx: &WindowContext) -> Option<&'static str> {
-        self.read(cx).icon_tooltip(cx)
-    }
-
-    fn toggle_action(&self, cx: &WindowContext) -> Box<dyn Action> {
-        self.read(cx).toggle_action()
-    }
-
-    fn icon_label(&self, cx: &WindowContext) -> Option<String> {
-        self.read(cx).icon_label(cx)
-    }
-
-    fn to_any(&self) -> AnyView {
-        self.clone().into()
-    }
-
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.read(cx).focus_handle(cx).clone()
-    }
-}
-
-impl From<&dyn PanelHandle> for AnyView {
-    fn from(val: &dyn PanelHandle) -> Self {
-        val.to_any()
-    }
-}
-
-pub struct Dock {
-    position: DockPosition,
-    panel_entries: Vec<PanelEntry>,
-    is_open: bool,
-    active_panel_index: usize,
-    focus_handle: FocusHandle,
-    _focus_subscription: Subscription,
-}
-
-impl FocusableView for Dock {
-    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
-#[serde(rename_all = "lowercase")]
-pub enum DockPosition {
-    Left,
-    Bottom,
-    Right,
-}
-
-impl DockPosition {
-    fn to_label(&self) -> &'static str {
-        match self {
-            Self::Left => "left",
-            Self::Bottom => "bottom",
-            Self::Right => "right",
-        }
-    }
-
-    // todo!()
-    // fn to_resize_handle_side(self) -> HandleSide {
-    //     match self {
-    //         Self::Left => HandleSide::Right,
-    //         Self::Bottom => HandleSide::Top,
-    //         Self::Right => HandleSide::Left,
-    //     }
-    // }
-
-    pub fn axis(&self) -> Axis {
-        match self {
-            Self::Left | Self::Right => Axis::Horizontal,
-            Self::Bottom => Axis::Vertical,
-        }
-    }
-}
-
-struct PanelEntry {
-    panel: Arc<dyn PanelHandle>,
-    // todo!()
-    // context_menu: View<ContextMenu>,
-    _subscriptions: [Subscription; 2],
-}
-
-pub struct PanelButtons {
-    dock: View<Dock>,
-}
-
-impl Dock {
-    pub fn new(position: DockPosition, cx: &mut ViewContext<Workspace>) -> View<Self> {
-        let focus_handle = cx.focus_handle();
-
-        let dock = cx.new_view(|cx: &mut ViewContext<Self>| {
-            let focus_subscription = cx.on_focus(&focus_handle, |dock, cx| {
-                if let Some(active_entry) = dock.panel_entries.get(dock.active_panel_index) {
-                    active_entry.panel.focus_handle(cx).focus(cx)
-                }
-            });
-            Self {
-                position,
-                panel_entries: Default::default(),
-                active_panel_index: 0,
-                is_open: false,
-                focus_handle: focus_handle.clone(),
-                _focus_subscription: focus_subscription,
-            }
-        });
-
-        cx.observe(&dock, move |workspace, dock, cx| {
-            if dock.read(cx).is_open() {
-                if let Some(panel) = dock.read(cx).active_panel() {
-                    if panel.is_zoomed(cx) {
-                        workspace.zoomed = Some(panel.to_any().downgrade());
-                        workspace.zoomed_position = Some(position);
-                        return;
-                    }
-                }
-            }
-            if workspace.zoomed_position == Some(position) {
-                workspace.zoomed = None;
-                workspace.zoomed_position = None;
-            }
-        })
-        .detach();
-
-        dock
-    }
-
-    pub fn position(&self) -> DockPosition {
-        self.position
-    }
-
-    pub fn is_open(&self) -> bool {
-        self.is_open
-    }
-
-    // todo!()
-    //     pub fn has_focus(&self, cx: &WindowContext) -> bool {
-    //         self.visible_panel()
-    //             .map_or(false, |panel| panel.has_focus(cx))
-    //     }
-
-    pub fn panel<T: Panel>(&self) -> Option<View<T>> {
-        self.panel_entries
-            .iter()
-            .find_map(|entry| entry.panel.to_any().clone().downcast().ok())
-    }
-
-    pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
-        self.panel_entries
-            .iter()
-            .position(|entry| entry.panel.to_any().downcast::<T>().is_ok())
-    }
-
-    pub fn panel_index_for_persistent_name(
-        &self,
-        ui_name: &str,
-        _cx: &AppContext,
-    ) -> Option<usize> {
-        self.panel_entries
-            .iter()
-            .position(|entry| entry.panel.persistent_name() == ui_name)
-    }
-
-    pub fn active_panel_index(&self) -> usize {
-        self.active_panel_index
-    }
-
-    pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
-        if open != self.is_open {
-            self.is_open = open;
-            if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
-                active_panel.panel.set_active(open, cx);
-            }
-
-            cx.notify();
-        }
-    }
-
-    pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
-        for entry in &mut self.panel_entries {
-            if entry.panel.panel_id() == panel.entity_id() {
-                if zoomed != entry.panel.is_zoomed(cx) {
-                    entry.panel.set_zoomed(zoomed, cx);
-                }
-            } else if entry.panel.is_zoomed(cx) {
-                entry.panel.set_zoomed(false, cx);
-            }
-        }
-
-        cx.notify();
-    }
-
-    pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
-        for entry in &mut self.panel_entries {
-            if entry.panel.is_zoomed(cx) {
-                entry.panel.set_zoomed(false, cx);
-            }
-        }
-    }
-
-    pub(crate) fn add_panel<T: Panel>(
-        &mut self,
-        panel: View<T>,
-        workspace: WeakView<Workspace>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let subscriptions = [
-            cx.observe(&panel, |_, _, cx| cx.notify()),
-            cx.subscribe(&panel, move |this, panel, event, cx| match event {
-                PanelEvent::ChangePosition => {
-                    let new_position = panel.read(cx).position(cx);
-
-                    let Ok(new_dock) = workspace.update(cx, |workspace, cx| {
-                        if panel.is_zoomed(cx) {
-                            workspace.zoomed_position = Some(new_position);
-                        }
-                        match new_position {
-                            DockPosition::Left => &workspace.left_dock,
-                            DockPosition::Bottom => &workspace.bottom_dock,
-                            DockPosition::Right => &workspace.right_dock,
-                        }
-                        .clone()
-                    }) else {
-                        return;
-                    };
-
-                    let was_visible = this.is_open()
-                        && this.visible_panel().map_or(false, |active_panel| {
-                            active_panel.panel_id() == Entity::entity_id(&panel)
-                        });
-
-                    this.remove_panel(&panel, cx);
-
-                    new_dock.update(cx, |new_dock, cx| {
-                        new_dock.add_panel(panel.clone(), workspace.clone(), cx);
-                        if was_visible {
-                            new_dock.set_open(true, cx);
-                            new_dock.activate_panel(new_dock.panels_len() - 1, cx);
-                        }
-                    });
-                }
-                PanelEvent::ZoomIn => {
-                    this.set_panel_zoomed(&panel.to_any(), true, cx);
-                    if !panel.focus_handle(cx).contains_focused(cx) {
-                        cx.focus_view(&panel);
-                    }
-                    workspace
-                        .update(cx, |workspace, cx| {
-                            workspace.zoomed = Some(panel.downgrade().into());
-                            workspace.zoomed_position = Some(panel.read(cx).position(cx));
-                        })
-                        .ok();
-                }
-                PanelEvent::ZoomOut => {
-                    this.set_panel_zoomed(&panel.to_any(), false, cx);
-                    workspace
-                        .update(cx, |workspace, cx| {
-                            if workspace.zoomed_position == Some(this.position) {
-                                workspace.zoomed = None;
-                                workspace.zoomed_position = None;
-                            }
-                            cx.notify();
-                        })
-                        .ok();
-                }
-                // todo!() we do not use this event in the production code (even in zed1), remove it
-                PanelEvent::Activate => {
-                    if let Some(ix) = this
-                        .panel_entries
-                        .iter()
-                        .position(|entry| entry.panel.panel_id() == Entity::entity_id(&panel))
-                    {
-                        this.set_open(true, cx);
-                        this.activate_panel(ix, cx);
-                        cx.focus_view(&panel);
-                    }
-                }
-                PanelEvent::Close => {
-                    if this
-                        .visible_panel()
-                        .map_or(false, |p| p.panel_id() == Entity::entity_id(&panel))
-                    {
-                        this.set_open(false, cx);
-                    }
-                }
-                PanelEvent::Focus => {}
-            }),
-        ];
-
-        // todo!()
-        // let dock_view_id = cx.view_id();
-        self.panel_entries.push(PanelEntry {
-            panel: Arc::new(panel),
-            // todo!()
-            // context_menu: cx.add_view(|cx| {
-            //     let mut menu = ContextMenu::new(dock_view_id, cx);
-            //     menu.set_position_mode(OverlayPositionMode::Local);
-            //     menu
-            // }),
-            _subscriptions: subscriptions,
-        });
-        cx.notify()
-    }
-
-    pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
-        if let Some(panel_ix) = self
-            .panel_entries
-            .iter()
-            .position(|entry| entry.panel.panel_id() == Entity::entity_id(panel))
-        {
-            if panel_ix == self.active_panel_index {
-                self.active_panel_index = 0;
-                self.set_open(false, cx);
-            } else if panel_ix < self.active_panel_index {
-                self.active_panel_index -= 1;
-            }
-            self.panel_entries.remove(panel_ix);
-            cx.notify();
-        }
-    }
-
-    pub fn panels_len(&self) -> usize {
-        self.panel_entries.len()
-    }
-
-    pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
-        if panel_ix != self.active_panel_index {
-            if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
-                active_panel.panel.set_active(false, cx);
-            }
-
-            self.active_panel_index = panel_ix;
-            if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
-                active_panel.panel.set_active(true, cx);
-            }
-
-            cx.notify();
-        }
-    }
-
-    pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
-        let entry = self.visible_entry()?;
-        Some(&entry.panel)
-    }
-
-    pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
-        Some(&self.panel_entries.get(self.active_panel_index)?.panel)
-    }
-
-    fn visible_entry(&self) -> Option<&PanelEntry> {
-        if self.is_open {
-            self.panel_entries.get(self.active_panel_index)
-        } else {
-            None
-        }
-    }
-
-    pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
-        let entry = self.visible_entry()?;
-        if entry.panel.is_zoomed(cx) {
-            Some(entry.panel.clone())
-        } else {
-            None
-        }
-    }
-
-    pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<Pixels> {
-        self.panel_entries
-            .iter()
-            .find(|entry| entry.panel.panel_id() == panel.panel_id())
-            .map(|entry| entry.panel.size(cx))
-    }
-
-    pub fn active_panel_size(&self, cx: &WindowContext) -> Option<Pixels> {
-        if self.is_open {
-            self.panel_entries
-                .get(self.active_panel_index)
-                .map(|entry| entry.panel.size(cx))
-        } else {
-            None
-        }
-    }
-
-    pub fn resize_active_panel(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
-        if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
-            let size = size.map(|size| size.max(RESIZE_HANDLE_SIZE));
-            entry.panel.set_size(size, cx);
-            cx.notify();
-        }
-    }
-
-    pub fn toggle_action(&self) -> Box<dyn Action> {
-        match self.position {
-            DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
-            DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
-            DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
-        }
-    }
-}
-
-impl Render for Dock {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        if let Some(entry) = self.visible_entry() {
-            let size = entry.panel.size(cx);
-
-            let position = self.position;
-            let mut handle = div()
-                .id("resize-handle")
-                .on_drag(DraggedDock(position), |dock, cx| {
-                    cx.stop_propagation();
-                    cx.new_view(|_| dock.clone())
-                })
-                .on_click(cx.listener(|v, e: &ClickEvent, cx| {
-                    if e.down.button == MouseButton::Left && e.down.click_count == 2 {
-                        v.resize_active_panel(None, cx);
-                        cx.stop_propagation();
-                    }
-                }))
-                .z_index(1)
-                .block_mouse();
-
-            match self.position() {
-                DockPosition::Left => {
-                    handle = handle
-                        .absolute()
-                        .right(px(0.))
-                        .top(px(0.))
-                        .h_full()
-                        .w(RESIZE_HANDLE_SIZE)
-                        .cursor_col_resize();
-                }
-                DockPosition::Bottom => {
-                    handle = handle
-                        .absolute()
-                        .top(px(0.))
-                        .left(px(0.))
-                        .w_full()
-                        .h(RESIZE_HANDLE_SIZE)
-                        .cursor_row_resize();
-                }
-                DockPosition::Right => {
-                    handle = handle
-                        .absolute()
-                        .top(px(0.))
-                        .left(px(0.))
-                        .h_full()
-                        .w(RESIZE_HANDLE_SIZE)
-                        .cursor_col_resize();
-                }
-            }
-
-            div()
-                .flex()
-                .bg(cx.theme().colors().panel_background)
-                .border_color(cx.theme().colors().border)
-                .overflow_hidden()
-                .map(|this| match self.position().axis() {
-                    Axis::Horizontal => this.w(size).h_full().flex_row(),
-                    Axis::Vertical => this.h(size).w_full().flex_col(),
-                })
-                .map(|this| match self.position() {
-                    DockPosition::Left => this.border_r(),
-                    DockPosition::Right => this.border_l(),
-                    DockPosition::Bottom => this.border_t(),
-                })
-                .child(
-                    div()
-                        .map(|this| match self.position().axis() {
-                            Axis::Horizontal => this.min_w(size).h_full(),
-                            Axis::Vertical => this.min_h(size).w_full(),
-                        })
-                        .child(entry.panel.to_any()),
-                )
-                .child(handle)
-        } else {
-            div()
-        }
-    }
-}
-
-impl PanelButtons {
-    pub fn new(dock: View<Dock>, cx: &mut ViewContext<Self>) -> Self {
-        cx.observe(&dock, |_, _, cx| cx.notify()).detach();
-        Self { dock }
-    }
-}
-
-impl Render for PanelButtons {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        // todo!()
-        let dock = self.dock.read(cx);
-        let active_index = dock.active_panel_index;
-        let is_open = dock.is_open;
-        let dock_position = dock.position;
-
-        let (menu_anchor, menu_attach) = match dock.position {
-            DockPosition::Left => (AnchorCorner::BottomLeft, AnchorCorner::TopLeft),
-            DockPosition::Bottom | DockPosition::Right => {
-                (AnchorCorner::BottomRight, AnchorCorner::TopRight)
-            }
-        };
-
-        let buttons = dock
-            .panel_entries
-            .iter()
-            .enumerate()
-            .filter_map(|(i, entry)| {
-                let icon = entry.panel.icon(cx)?;
-                let icon_tooltip = entry.panel.icon_tooltip(cx)?;
-                let name = entry.panel.persistent_name();
-                let panel = entry.panel.clone();
-
-                let is_active_button = i == active_index && is_open;
-
-                let (action, tooltip) = if is_active_button {
-                    let action = dock.toggle_action();
-
-                    let tooltip: SharedString =
-                        format!("Close {} dock", dock.position.to_label()).into();
-
-                    (action, tooltip)
-                } else {
-                    let action = entry.panel.toggle_action(cx);
-
-                    (action, icon_tooltip.into())
-                };
-
-                Some(
-                    right_click_menu(name)
-                        .menu(move |cx| {
-                            const POSITIONS: [DockPosition; 3] = [
-                                DockPosition::Left,
-                                DockPosition::Right,
-                                DockPosition::Bottom,
-                            ];
-
-                            ContextMenu::build(cx, |mut menu, cx| {
-                                for position in POSITIONS {
-                                    if position != dock_position
-                                        && panel.position_is_valid(position, cx)
-                                    {
-                                        let panel = panel.clone();
-                                        menu = menu.entry(position.to_label(), None, move |cx| {
-                                            panel.set_position(position, cx);
-                                        })
-                                    }
-                                }
-                                menu
-                            })
-                        })
-                        .anchor(menu_anchor)
-                        .attach(menu_attach)
-                        .trigger(
-                            IconButton::new(name, icon)
-                                .icon_size(IconSize::Small)
-                                .selected(is_active_button)
-                                .on_click({
-                                    let action = action.boxed_clone();
-                                    move |_, cx| cx.dispatch_action(action.boxed_clone())
-                                })
-                                .tooltip(move |cx| {
-                                    Tooltip::for_action(tooltip.clone(), &*action, cx)
-                                }),
-                        ),
-                )
-            });
-
-        h_stack().gap_0p5().children(buttons)
-    }
-}
-
-impl StatusItemView for PanelButtons {
-    fn set_active_pane_item(
-        &mut self,
-        _active_pane_item: Option<&dyn crate::ItemHandle>,
-        _cx: &mut ViewContext<Self>,
-    ) {
-        // Nothing to do, panel buttons don't depend on the active center item
-    }
-}
-
-#[cfg(any(test, feature = "test-support"))]
-pub mod test {
-    use super::*;
-    use gpui::{actions, div, ViewContext, WindowContext};
-
-    pub struct TestPanel {
-        pub position: DockPosition,
-        pub zoomed: bool,
-        pub active: bool,
-        pub focus_handle: FocusHandle,
-        pub size: Pixels,
-    }
-    actions!(test, [ToggleTestPanel]);
-
-    impl EventEmitter<PanelEvent> for TestPanel {}
-
-    impl TestPanel {
-        pub fn new(position: DockPosition, cx: &mut WindowContext) -> Self {
-            Self {
-                position,
-                zoomed: false,
-                active: false,
-                focus_handle: cx.focus_handle(),
-                size: px(300.),
-            }
-        }
-    }
-
-    impl Render for TestPanel {
-        fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
-            div()
-        }
-    }
-
-    impl Panel for TestPanel {
-        fn persistent_name() -> &'static str {
-            "TestPanel"
-        }
-
-        fn position(&self, _: &gpui::WindowContext) -> super::DockPosition {
-            self.position
-        }
-
-        fn position_is_valid(&self, _: super::DockPosition) -> bool {
-            true
-        }
-
-        fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
-            self.position = position;
-            cx.emit(PanelEvent::ChangePosition);
-        }
-
-        fn size(&self, _: &WindowContext) -> Pixels {
-            self.size
-        }
-
-        fn set_size(&mut self, size: Option<Pixels>, _: &mut ViewContext<Self>) {
-            self.size = size.unwrap_or(px(300.));
-        }
-
-        fn icon(&self, _: &WindowContext) -> Option<ui::Icon> {
-            None
-        }
-
-        fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
-            None
-        }
-
-        fn toggle_action(&self) -> Box<dyn Action> {
-            ToggleTestPanel.boxed_clone()
-        }
-
-        fn is_zoomed(&self, _: &WindowContext) -> bool {
-            self.zoomed
-        }
-
-        fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
-            self.zoomed = zoomed;
-        }
-
-        fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
-            self.active = active;
-        }
-    }
-
-    impl FocusableView for TestPanel {
-        fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
-            self.focus_handle.clone()
-        }
-    }
-}

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

@@ -1,1070 +0,0 @@
-use crate::{
-    pane::{self, Pane},
-    persistence::model::ItemId,
-    searchable::SearchableItemHandle,
-    workspace_settings::{AutosaveSetting, WorkspaceSettings},
-    DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
-    ViewId, Workspace, WorkspaceId,
-};
-use anyhow::Result;
-use client::{
-    proto::{self, PeerId},
-    Client,
-};
-use gpui::{
-    AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, FocusableView,
-    HighlightStyle, Model, Pixels, Point, SharedString, Task, View, ViewContext, WeakView,
-    WindowContext,
-};
-use project::{Project, ProjectEntryId, ProjectPath};
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-use smallvec::SmallVec;
-use std::{
-    any::{Any, TypeId},
-    cell::RefCell,
-    ops::Range,
-    path::PathBuf,
-    rc::Rc,
-    sync::{
-        atomic::{AtomicBool, Ordering},
-        Arc,
-    },
-    time::Duration,
-};
-use theme::Theme;
-
-#[derive(Deserialize)]
-pub struct ItemSettings {
-    pub git_status: bool,
-    pub close_position: ClosePosition,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "lowercase")]
-pub enum ClosePosition {
-    Left,
-    #[default]
-    Right,
-}
-
-impl ClosePosition {
-    pub fn right(&self) -> bool {
-        match self {
-            ClosePosition::Left => false,
-            ClosePosition::Right => true,
-        }
-    }
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ItemSettingsContent {
-    git_status: Option<bool>,
-    close_position: Option<ClosePosition>,
-}
-
-impl Settings for ItemSettings {
-    const KEY: Option<&'static str> = Some("tabs");
-
-    type FileContent = ItemSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut AppContext,
-    ) -> Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}
-
-#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
-pub enum ItemEvent {
-    CloseItem,
-    UpdateTab,
-    UpdateBreadcrumbs,
-    Edit,
-}
-
-// TODO: Combine this with existing HighlightedText struct?
-pub struct BreadcrumbText {
-    pub text: String,
-    pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
-}
-
-pub trait Item: FocusableView + EventEmitter<Self::Event> {
-    type Event;
-
-    fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
-    fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
-    fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
-        false
-    }
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
-        None
-    }
-    fn tab_description(&self, _: usize, _: &AppContext) -> Option<SharedString> {
-        None
-    }
-    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement;
-
-    /// (model id, Item)
-    fn for_each_project_item(
-        &self,
-        _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project::Item),
-    ) {
-    }
-    fn is_singleton(&self, _cx: &AppContext) -> bool {
-        false
-    }
-    fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
-    fn clone_on_split(
-        &self,
-        _workspace_id: WorkspaceId,
-        _: &mut ViewContext<Self>,
-    ) -> Option<View<Self>>
-    where
-        Self: Sized,
-    {
-        None
-    }
-    fn is_dirty(&self, _: &AppContext) -> bool {
-        false
-    }
-    fn has_conflict(&self, _: &AppContext) -> bool {
-        false
-    }
-    fn can_save(&self, _cx: &AppContext) -> bool {
-        false
-    }
-    fn save(&mut self, _project: Model<Project>, _cx: &mut ViewContext<Self>) -> Task<Result<()>> {
-        unimplemented!("save() must be implemented if can_save() returns true")
-    }
-    fn save_as(
-        &mut self,
-        _project: Model<Project>,
-        _abs_path: PathBuf,
-        _cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        unimplemented!("save_as() must be implemented if can_save() returns true")
-    }
-    fn reload(
-        &mut self,
-        _project: Model<Project>,
-        _cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        unimplemented!("reload() must be implemented if can_save() returns true")
-    }
-
-    fn to_item_events(event: &Self::Event, f: impl FnMut(ItemEvent));
-
-    fn act_as_type<'a>(
-        &'a self,
-        type_id: TypeId,
-        self_handle: &'a View<Self>,
-        _: &'a AppContext,
-    ) -> Option<AnyView> {
-        if TypeId::of::<Self>() == type_id {
-            Some(self_handle.clone().into())
-        } else {
-            None
-        }
-    }
-
-    fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
-        None
-    }
-
-    fn breadcrumb_location(&self) -> ToolbarItemLocation {
-        ToolbarItemLocation::Hidden
-    }
-
-    fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        None
-    }
-
-    fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
-
-    fn serialized_item_kind() -> Option<&'static str> {
-        None
-    }
-
-    fn deserialize(
-        _project: Model<Project>,
-        _workspace: WeakView<Workspace>,
-        _workspace_id: WorkspaceId,
-        _item_id: ItemId,
-        _cx: &mut ViewContext<Pane>,
-    ) -> Task<Result<View<Self>>> {
-        unimplemented!(
-            "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
-        )
-    }
-    fn show_toolbar(&self) -> bool {
-        true
-    }
-    fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
-        None
-    }
-}
-
-pub trait ItemHandle: 'static + Send {
-    fn subscribe_to_item_events(
-        &self,
-        cx: &mut WindowContext,
-        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
-    ) -> gpui::Subscription;
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle;
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString>;
-    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString>;
-    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement;
-    fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement;
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
-    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
-    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]>;
-    fn for_each_project_item(
-        &self,
-        _: &AppContext,
-        _: &mut dyn FnMut(EntityId, &dyn project::Item),
-    );
-    fn is_singleton(&self, cx: &AppContext) -> bool;
-    fn boxed_clone(&self) -> Box<dyn ItemHandle>;
-    fn clone_on_split(
-        &self,
-        workspace_id: WorkspaceId,
-        cx: &mut WindowContext,
-    ) -> Option<Box<dyn ItemHandle>>;
-    fn added_to_pane(
-        &self,
-        workspace: &mut Workspace,
-        pane: View<Pane>,
-        cx: &mut ViewContext<Workspace>,
-    );
-    fn deactivated(&self, cx: &mut WindowContext);
-    fn workspace_deactivated(&self, cx: &mut WindowContext);
-    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
-    fn item_id(&self) -> EntityId;
-    fn to_any(&self) -> AnyView;
-    fn is_dirty(&self, cx: &AppContext) -> bool;
-    fn has_conflict(&self, cx: &AppContext) -> bool;
-    fn can_save(&self, cx: &AppContext) -> bool;
-    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
-    fn save_as(
-        &self,
-        project: Model<Project>,
-        abs_path: PathBuf,
-        cx: &mut WindowContext,
-    ) -> Task<Result<()>>;
-    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
-    fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
-    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
-    fn on_release(
-        &self,
-        cx: &mut AppContext,
-        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
-    ) -> gpui::Subscription;
-    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
-    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
-    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
-    fn serialized_item_kind(&self) -> Option<&'static str>;
-    fn show_toolbar(&self, cx: &AppContext) -> bool;
-    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
-}
-
-pub trait WeakItemHandle: Send + Sync {
-    fn id(&self) -> EntityId;
-    fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
-}
-
-impl dyn ItemHandle {
-    pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
-        self.to_any().downcast().ok()
-    }
-
-    pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
-        self.act_as_type(TypeId::of::<V>(), cx)
-            .and_then(|t| t.downcast().ok())
-    }
-}
-
-impl<T: Item> ItemHandle for View<T> {
-    fn subscribe_to_item_events(
-        &self,
-        cx: &mut WindowContext,
-        handler: Box<dyn Fn(ItemEvent, &mut WindowContext)>,
-    ) -> gpui::Subscription {
-        cx.subscribe(self, move |_, event, cx| {
-            T::to_item_events(event, |item_event| handler(item_event, cx));
-        })
-    }
-
-    fn focus_handle(&self, cx: &WindowContext) -> FocusHandle {
-        self.focus_handle(cx)
-    }
-
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
-        self.read(cx).tab_tooltip_text(cx)
-    }
-
-    fn tab_description(&self, detail: usize, cx: &AppContext) -> Option<SharedString> {
-        self.read(cx).tab_description(detail, cx)
-    }
-
-    fn tab_content(&self, detail: Option<usize>, selected: bool, cx: &WindowContext) -> AnyElement {
-        self.read(cx).tab_content(detail, selected, cx)
-    }
-
-    fn dragged_tab_content(&self, detail: Option<usize>, cx: &WindowContext) -> AnyElement {
-        self.read(cx).tab_content(detail, true, cx)
-    }
-
-    fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
-        let this = self.read(cx);
-        let mut result = None;
-        if this.is_singleton(cx) {
-            this.for_each_project_item(cx, &mut |_, item| {
-                result = item.project_path(cx);
-            });
-        }
-        result
-    }
-
-    fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]> {
-        let mut result = SmallVec::new();
-        self.read(cx).for_each_project_item(cx, &mut |_, item| {
-            if let Some(id) = item.entry_id(cx) {
-                result.push(id);
-            }
-        });
-        result
-    }
-
-    fn project_item_model_ids(&self, cx: &AppContext) -> SmallVec<[EntityId; 3]> {
-        let mut result = SmallVec::new();
-        self.read(cx).for_each_project_item(cx, &mut |id, _| {
-            result.push(id);
-        });
-        result
-    }
-
-    fn for_each_project_item(
-        &self,
-        cx: &AppContext,
-        f: &mut dyn FnMut(EntityId, &dyn project::Item),
-    ) {
-        self.read(cx).for_each_project_item(cx, f)
-    }
-
-    fn is_singleton(&self, cx: &AppContext) -> bool {
-        self.read(cx).is_singleton(cx)
-    }
-
-    fn boxed_clone(&self) -> Box<dyn ItemHandle> {
-        Box::new(self.clone())
-    }
-
-    fn clone_on_split(
-        &self,
-        workspace_id: WorkspaceId,
-        cx: &mut WindowContext,
-    ) -> Option<Box<dyn ItemHandle>> {
-        self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
-            .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
-    }
-
-    fn added_to_pane(
-        &self,
-        workspace: &mut Workspace,
-        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);
-            this.added_to_workspace(workspace, cx);
-        });
-
-        if let Some(followed_item) = self.to_followable_item_handle(cx) {
-            if let Some(message) = followed_item.to_state_proto(cx) {
-                workspace.update_followers(
-                    followed_item.is_project_item(cx),
-                    proto::update_followers::Variant::CreateView(proto::View {
-                        id: followed_item
-                            .remote_id(&workspace.app_state.client, cx)
-                            .map(|id| id.to_proto()),
-                        variant: Some(message),
-                        leader_id: workspace.leader_for_pane(&pane),
-                    }),
-                    cx,
-                );
-            }
-        }
-
-        if workspace
-            .panes_by_item
-            .insert(self.item_id(), pane.downgrade())
-            .is_none()
-        {
-            let mut pending_autosave = DelayedDebouncedEditAction::new();
-            let pending_update = Rc::new(RefCell::new(None));
-            let pending_update_scheduled = Arc::new(AtomicBool::new(false));
-
-            let mut event_subscription =
-                Some(cx.subscribe(self, move |workspace, item, event, cx| {
-                    let pane = if let Some(pane) = workspace
-                        .panes_by_item
-                        .get(&item.item_id())
-                        .and_then(|pane| pane.upgrade())
-                    {
-                        pane
-                    } else {
-                        log::error!("unexpected item event after pane was dropped");
-                        return;
-                    };
-
-                    if let Some(item) = item.to_followable_item_handle(cx) {
-                        let is_project_item = item.is_project_item(cx);
-                        let leader_id = workspace.leader_for_pane(&pane);
-
-                        let follow_event = item.to_follow_event(event);
-                        if leader_id.is_some()
-                            && matches!(follow_event, Some(FollowEvent::Unfollow))
-                        {
-                            workspace.unfollow(&pane, cx);
-                        }
-
-                        if item.add_event_to_update_proto(
-                            event,
-                            &mut *pending_update.borrow_mut(),
-                            cx,
-                        ) && !pending_update_scheduled.load(Ordering::SeqCst)
-                        {
-                            pending_update_scheduled.store(true, Ordering::SeqCst);
-                            cx.on_next_frame({
-                                let pending_update = pending_update.clone();
-                                let pending_update_scheduled = pending_update_scheduled.clone();
-                                move |this, cx| {
-                                    pending_update_scheduled.store(false, Ordering::SeqCst);
-                                    this.update_followers(
-                                        is_project_item,
-                                        proto::update_followers::Variant::UpdateView(
-                                            proto::UpdateView {
-                                                id: item
-                                                    .remote_id(&this.app_state.client, cx)
-                                                    .map(|id| id.to_proto()),
-                                                variant: pending_update.borrow_mut().take(),
-                                                leader_id,
-                                            },
-                                        ),
-                                        cx,
-                                    );
-                                }
-                            });
-                        }
-                    }
-
-                    T::to_item_events(event, |event| match event {
-                        ItemEvent::CloseItem => {
-                            pane.update(cx, |pane, cx| {
-                                pane.close_item_by_id(item.item_id(), crate::SaveIntent::Close, cx)
-                            })
-                            .detach_and_log_err(cx);
-                            return;
-                        }
-
-                        ItemEvent::UpdateTab => {
-                            pane.update(cx, |_, cx| {
-                                cx.emit(pane::Event::ChangeItemTitle);
-                                cx.notify();
-                            });
-                        }
-
-                        ItemEvent::Edit => {
-                            let autosave = WorkspaceSettings::get_global(cx).autosave;
-                            if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
-                                let delay = Duration::from_millis(milliseconds);
-                                let item = item.clone();
-                                pending_autosave.fire_new(delay, cx, move |workspace, cx| {
-                                    Pane::autosave_item(&item, workspace.project().clone(), cx)
-                                });
-                            }
-                        }
-
-                        _ => {}
-                    });
-                }));
-
-            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, _, _| {
-                workspace.panes_by_item.remove(&item_id);
-                event_subscription.take();
-            })
-            .detach();
-        }
-
-        cx.defer(|workspace, cx| {
-            workspace.serialize_workspace(cx);
-        });
-    }
-
-    fn deactivated(&self, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.deactivated(cx));
-    }
-
-    fn workspace_deactivated(&self, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.workspace_deactivated(cx));
-    }
-
-    fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool {
-        self.update(cx, |this, cx| this.navigate(data, cx))
-    }
-
-    fn item_id(&self) -> EntityId {
-        self.entity_id()
-    }
-
-    fn to_any(&self) -> AnyView {
-        self.clone().into()
-    }
-
-    fn is_dirty(&self, cx: &AppContext) -> bool {
-        self.read(cx).is_dirty(cx)
-    }
-
-    fn has_conflict(&self, cx: &AppContext) -> bool {
-        self.read(cx).has_conflict(cx)
-    }
-
-    fn can_save(&self, cx: &AppContext) -> bool {
-        self.read(cx).can_save(cx)
-    }
-
-    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
-        self.update(cx, |item, cx| item.save(project, cx))
-    }
-
-    fn save_as(
-        &self,
-        project: Model<Project>,
-        abs_path: PathBuf,
-        cx: &mut WindowContext,
-    ) -> Task<anyhow::Result<()>> {
-        self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
-    }
-
-    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
-        self.update(cx, |item, cx| item.reload(project, cx))
-    }
-
-    fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
-        self.read(cx).act_as_type(type_id, self, cx)
-    }
-
-    fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
-        if cx.has_global::<FollowableItemBuilders>() {
-            let builders = cx.global::<FollowableItemBuilders>();
-            let item = self.to_any();
-            Some(builders.get(&item.entity_type())?.1(&item))
-        } else {
-            None
-        }
-    }
-
-    fn on_release(
-        &self,
-        cx: &mut AppContext,
-        callback: Box<dyn FnOnce(&mut AppContext) + Send>,
-    ) -> gpui::Subscription {
-        cx.observe_release(self, move |_, cx| callback(cx))
-    }
-
-    fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
-        self.read(cx).as_searchable(self)
-    }
-
-    fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation {
-        self.read(cx).breadcrumb_location()
-    }
-
-    fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
-        self.read(cx).breadcrumbs(theme, cx)
-    }
-
-    fn serialized_item_kind(&self) -> Option<&'static str> {
-        T::serialized_item_kind()
-    }
-
-    fn show_toolbar(&self, cx: &AppContext) -> bool {
-        self.read(cx).show_toolbar()
-    }
-
-    fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
-        self.read(cx).pixel_position_of_cursor(cx)
-    }
-}
-
-impl From<Box<dyn ItemHandle>> for AnyView {
-    fn from(val: Box<dyn ItemHandle>) -> Self {
-        val.to_any()
-    }
-}
-
-impl From<&Box<dyn ItemHandle>> for AnyView {
-    fn from(val: &Box<dyn ItemHandle>) -> Self {
-        val.to_any()
-    }
-}
-
-impl Clone for Box<dyn ItemHandle> {
-    fn clone(&self) -> Box<dyn ItemHandle> {
-        self.boxed_clone()
-    }
-}
-
-impl<T: Item> WeakItemHandle for WeakView<T> {
-    fn id(&self) -> EntityId {
-        self.entity_id()
-    }
-
-    fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
-        self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
-    }
-}
-
-pub trait ProjectItem: Item {
-    type Item: project::Item;
-
-    fn for_project_item(
-        project: Model<Project>,
-        item: Model<Self::Item>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self
-    where
-        Self: Sized;
-}
-
-pub enum FollowEvent {
-    Unfollow,
-}
-
-pub trait FollowableItem: Item {
-    fn remote_id(&self) -> Option<ViewId>;
-    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 WindowContext,
-    ) -> Option<Task<Result<View<Self>>>>;
-    fn to_follow_event(event: &Self::Event) -> Option<FollowEvent>;
-    fn add_event_to_update_proto(
-        &self,
-        event: &Self::Event,
-        update: &mut Option<proto::update_view::Variant>,
-        cx: &WindowContext,
-    ) -> bool;
-    fn apply_update_proto(
-        &mut self,
-        project: &Model<Project>,
-        message: proto::update_view::Variant,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>>;
-    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: &WindowContext) -> Option<ViewId>;
-    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext);
-    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: &WindowContext,
-    ) -> bool;
-    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent>;
-    fn apply_update_proto(
-        &self,
-        project: &Model<Project>,
-        message: proto::update_view::Variant,
-        cx: &mut WindowContext,
-    ) -> Task<Result<()>>;
-    fn is_project_item(&self, cx: &WindowContext) -> bool;
-}
-
-impl<T: FollowableItem> FollowableItemHandle for View<T> {
-    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,
-                id: self.item_id().as_u64(),
-            })
-        })
-    }
-
-    fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
-    }
-
-    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
-        self.read(cx).to_state_proto(cx)
-    }
-
-    fn add_event_to_update_proto(
-        &self,
-        event: &dyn Any,
-        update: &mut Option<proto::update_view::Variant>,
-        cx: &WindowContext,
-    ) -> bool {
-        if let Some(event) = event.downcast_ref() {
-            self.read(cx).add_event_to_update_proto(event, update, cx)
-        } else {
-            false
-        }
-    }
-
-    fn to_follow_event(&self, event: &dyn Any) -> Option<FollowEvent> {
-        T::to_follow_event(event.downcast_ref()?)
-    }
-
-    fn apply_update_proto(
-        &self,
-        project: &Model<Project>,
-        message: proto::update_view::Variant,
-        cx: &mut WindowContext,
-    ) -> Task<Result<()>> {
-        self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
-    }
-
-    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::{
-        AnyElement, AppContext, Context as _, EntityId, EventEmitter, FocusableView,
-        InteractiveElement, 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.new_model(|_| Self {
-                entry_id,
-                project_path,
-            })
-        }
-
-        pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
-            cx.new_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 {
-        fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
-            gpui::div().track_focus(&self.focus_handle)
-        }
-    }
-
-    impl EventEmitter<ItemEvent> for TestItem {}
-
-    impl FocusableView for TestItem {
-        fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
-            self.focus_handle.clone()
-        }
-    }
-
-    impl Item for TestItem {
-        type Event = ItemEvent;
-
-        fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
-            f(*event)
-        }
-
-        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>,
-            _selected: bool,
-            _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.new_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.new_view(|cx| Self::new_deserialized(workspace_id, cx));
-            Task::Ready(Some(anyhow::Ok(view)))
-        }
-    }
-}

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

@@ -1,395 +0,0 @@
-use crate::{Toast, Workspace};
-use collections::HashMap;
-use gpui::{
-    AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
-    View, ViewContext, VisualContext,
-};
-use std::{any::TypeId, ops::DerefMut};
-
-pub fn init(cx: &mut AppContext) {
-    cx.set_global(NotificationTracker::new());
-    // todo!()
-    // simple_message_notification::init(cx);
-}
-
-pub trait Notification: EventEmitter<DismissEvent> + Render {}
-
-impl<V: EventEmitter<DismissEvent> + Render> Notification for V {}
-
-pub trait NotificationHandle: Send {
-    fn id(&self) -> EntityId;
-    fn to_any(&self) -> AnyView;
-}
-
-impl<T: Notification> NotificationHandle for View<T> {
-    fn id(&self) -> EntityId {
-        self.entity_id()
-    }
-
-    fn to_any(&self) -> AnyView {
-        self.clone().into()
-    }
-}
-
-impl From<&dyn NotificationHandle> for AnyView {
-    fn from(val: &dyn NotificationHandle) -> Self {
-        val.to_any()
-    }
-}
-
-pub(crate) struct NotificationTracker {
-    notifications_sent: HashMap<TypeId, Vec<usize>>,
-}
-
-impl std::ops::Deref for NotificationTracker {
-    type Target = HashMap<TypeId, Vec<usize>>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.notifications_sent
-    }
-}
-
-impl DerefMut for NotificationTracker {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.notifications_sent
-    }
-}
-
-impl NotificationTracker {
-    fn new() -> Self {
-        Self {
-            notifications_sent: Default::default(),
-        }
-    }
-}
-
-impl Workspace {
-    pub fn has_shown_notification_once<V: Notification>(
-        &self,
-        id: usize,
-        cx: &ViewContext<Self>,
-    ) -> bool {
-        cx.global::<NotificationTracker>()
-            .get(&TypeId::of::<V>())
-            .map(|ids| ids.contains(&id))
-            .unwrap_or(false)
-    }
-
-    pub fn show_notification_once<V: Notification>(
-        &mut self,
-        id: usize,
-        cx: &mut ViewContext<Self>,
-        build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
-    ) {
-        if !self.has_shown_notification_once::<V>(id, cx) {
-            let tracker = cx.global_mut::<NotificationTracker>();
-            let entry = tracker.entry(TypeId::of::<V>()).or_default();
-            entry.push(id);
-            self.show_notification::<V>(id, cx, build_notification)
-        }
-    }
-
-    pub fn show_notification<V: Notification>(
-        &mut self,
-        id: usize,
-        cx: &mut ViewContext<Self>,
-        build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
-    ) {
-        let type_id = TypeId::of::<V>();
-        if self
-            .notifications
-            .iter()
-            .all(|(existing_type_id, existing_id, _)| {
-                (*existing_type_id, *existing_id) != (type_id, id)
-            })
-        {
-            let notification = build_notification(cx);
-            cx.subscribe(&notification, move |this, _, _: &DismissEvent, cx| {
-                this.dismiss_notification_internal(type_id, id, cx);
-            })
-            .detach();
-            self.notifications
-                .push((type_id, id, Box::new(notification)));
-            cx.notify();
-        }
-    }
-
-    pub fn show_error<E>(&mut self, err: &E, cx: &mut ViewContext<Self>)
-    where
-        E: std::fmt::Debug,
-    {
-        self.show_notification(0, cx, |cx| {
-            cx.new_view(|_cx| {
-                simple_message_notification::MessageNotification::new(format!("Error: {err:?}"))
-            })
-        });
-    }
-
-    pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
-        let type_id = TypeId::of::<V>();
-
-        self.dismiss_notification_internal(type_id, id, cx)
-    }
-
-    pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
-        self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
-        self.show_notification(toast.id, cx, |cx| {
-            cx.new_view(|_cx| match toast.on_click.as_ref() {
-                Some((click_msg, on_click)) => {
-                    let on_click = on_click.clone();
-                    simple_message_notification::MessageNotification::new(toast.msg.clone())
-                        .with_click_message(click_msg.clone())
-                        .on_click(move |cx| on_click(cx))
-                }
-                None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
-            })
-        })
-    }
-
-    pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
-        self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
-    }
-
-    fn dismiss_notification_internal(
-        &mut self,
-        type_id: TypeId,
-        id: usize,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.notifications
-            .retain(|(existing_type_id, existing_id, _)| {
-                if (*existing_type_id, *existing_id) == (type_id, id) {
-                    cx.notify();
-                    false
-                } else {
-                    true
-                }
-            });
-    }
-}
-
-pub mod simple_message_notification {
-    use gpui::{
-        div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,
-        StatefulInteractiveElement, Styled, ViewContext,
-    };
-    use std::sync::Arc;
-    use ui::prelude::*;
-    use ui::{h_stack, v_stack, Button, Icon, IconElement, Label, StyledExt};
-
-    pub struct MessageNotification {
-        message: SharedString,
-        on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>)>>,
-        click_message: Option<SharedString>,
-    }
-
-    impl EventEmitter<DismissEvent> for MessageNotification {}
-
-    impl MessageNotification {
-        pub fn new<S>(message: S) -> MessageNotification
-        where
-            S: Into<SharedString>,
-        {
-            Self {
-                message: message.into(),
-                on_click: None,
-                click_message: None,
-            }
-        }
-
-        pub fn with_click_message<S>(mut self, message: S) -> Self
-        where
-            S: Into<SharedString>,
-        {
-            self.click_message = Some(message.into());
-            self
-        }
-
-        pub fn on_click<F>(mut self, on_click: F) -> Self
-        where
-            F: 'static + Fn(&mut ViewContext<Self>),
-        {
-            self.on_click = Some(Arc::new(on_click));
-            self
-        }
-
-        pub fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
-            cx.emit(DismissEvent);
-        }
-    }
-
-    impl Render for MessageNotification {
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-            v_stack()
-                .elevation_3(cx)
-                .p_4()
-                .child(
-                    h_stack()
-                        .justify_between()
-                        .child(div().max_w_80().child(Label::new(self.message.clone())))
-                        .child(
-                            div()
-                                .id("cancel")
-                                .child(IconElement::new(Icon::Close))
-                                .cursor_pointer()
-                                .on_click(cx.listener(|this, _, cx| this.dismiss(cx))),
-                        ),
-                )
-                .children(self.click_message.iter().map(|message| {
-                    Button::new(message.clone(), message.clone()).on_click(cx.listener(
-                        |this, _, cx| {
-                            if let Some(on_click) = this.on_click.as_ref() {
-                                (on_click)(cx)
-                            };
-                            this.dismiss(cx)
-                        },
-                    ))
-                }))
-        }
-    }
-    // todo!()
-    //     impl View for MessageNotification {
-    //         fn ui_name() -> &'static str {
-    //             "MessageNotification"
-    //         }
-
-    //         fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-    //             let theme = theme2::current(cx).clone();
-    //             let theme = &theme.simple_message_notification;
-
-    //             enum MessageNotificationTag {}
-
-    //             let click_message = self.click_message.clone();
-    //             let message = match &self.message {
-    //                 NotificationMessage::Text(text) => {
-    //                     Text::new(text.to_owned(), theme.message.text.clone()).into_any()
-    //                 }
-    //                 NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
-    //             };
-    //             let on_click = self.on_click.clone();
-    //             let has_click_action = on_click.is_some();
-
-    //             Flex::column()
-    //                 .with_child(
-    //                     Flex::row()
-    //                         .with_child(
-    //                             message
-    //                                 .contained()
-    //                                 .with_style(theme.message.container)
-    //                                 .aligned()
-    //                                 .top()
-    //                                 .left()
-    //                                 .flex(1., true),
-    //                         )
-    //                         .with_child(
-    //                             MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
-    //                                 let style = theme.dismiss_button.style_for(state);
-    //                                 Svg::new("icons/x.svg")
-    //                                     .with_color(style.color)
-    //                                     .constrained()
-    //                                     .with_width(style.icon_width)
-    //                                     .aligned()
-    //                                     .contained()
-    //                                     .with_style(style.container)
-    //                                     .constrained()
-    //                                     .with_width(style.button_width)
-    //                                     .with_height(style.button_width)
-    //                             })
-    //                             .with_padding(Padding::uniform(5.))
-    //                             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                                 this.dismiss(&Default::default(), cx);
-    //                             })
-    //                             .with_cursor_style(CursorStyle::PointingHand)
-    //                             .aligned()
-    //                             .constrained()
-    //                             .with_height(cx.font_cache().line_height(theme.message.text.font_size))
-    //                             .aligned()
-    //                             .top()
-    //                             .flex_float(),
-    //                         ),
-    //                 )
-    //                 .with_children({
-    //                     click_message
-    //                         .map(|click_message| {
-    //                             MouseEventHandler::new::<MessageNotificationTag, _>(
-    //                                 0,
-    //                                 cx,
-    //                                 |state, _| {
-    //                                     let style = theme.action_message.style_for(state);
-
-    //                                     Flex::row()
-    //                                         .with_child(
-    //                                             Text::new(click_message, style.text.clone())
-    //                                                 .contained()
-    //                                                 .with_style(style.container),
-    //                                         )
-    //                                         .contained()
-    //                                 },
-    //                             )
-    //                             .on_click(MouseButton::Left, move |_, this, cx| {
-    //                                 if let Some(on_click) = on_click.as_ref() {
-    //                                     on_click(cx);
-    //                                     this.dismiss(&Default::default(), cx);
-    //                                 }
-    //                             })
-    //                             // Since we're not using a proper overlay, we have to capture these extra events
-    //                             .on_down(MouseButton::Left, |_, _, _| {})
-    //                             .on_up(MouseButton::Left, |_, _, _| {})
-    //                             .with_cursor_style(if has_click_action {
-    //                                 CursorStyle::PointingHand
-    //                             } else {
-    //                                 CursorStyle::Arrow
-    //                             })
-    //                         })
-    //                         .into_iter()
-    //                 })
-    //                 .into_any()
-    //         }
-    //     }
-}
-
-pub trait NotifyResultExt {
-    type Ok;
-
-    fn notify_err(
-        self,
-        workspace: &mut Workspace,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Option<Self::Ok>;
-
-    fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<Self::Ok>;
-}
-
-impl<T, E> NotifyResultExt for Result<T, E>
-where
-    E: std::fmt::Debug,
-{
-    type Ok = T;
-
-    fn notify_err(self, workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<T> {
-        match self {
-            Ok(value) => Some(value),
-            Err(err) => {
-                log::error!("TODO {err:?}");
-                workspace.show_error(&err, cx);
-                None
-            }
-        }
-    }
-
-    fn notify_async_err(self, cx: &mut AsyncWindowContext) -> Option<T> {
-        match self {
-            Ok(value) => Some(value),
-            Err(err) => {
-                log::error!("TODO {err:?}");
-                cx.update(|view, cx| {
-                    if let Ok(workspace) = view.downcast::<Workspace>() {
-                        workspace.update(cx, |workspace, cx| workspace.show_error(&err, cx))
-                    }
-                })
-                .ok();
-                None
-            }
-        }
-    }
-}

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

@@ -1,2759 +0,0 @@
-use crate::{
-    item::{ClosePosition, Item, ItemHandle, ItemSettings, WeakItemHandle},
-    toolbar::Toolbar,
-    workspace_settings::{AutosaveSetting, WorkspaceSettings},
-    NewCenterTerminal, NewFile, NewSearch, SplitDirection, ToggleZoom, Workspace,
-};
-use anyhow::Result;
-use collections::{HashMap, HashSet, VecDeque};
-use gpui::{
-    actions, impl_actions, overlay, prelude::*, Action, AnchorCorner, AnyElement, AppContext,
-    AsyncWindowContext, DismissEvent, Div, DragMoveEvent, EntityId, EventEmitter, FocusHandle,
-    FocusableView, Model, MouseButton, NavigationDirection, Pixels, Point, PromptLevel, Render,
-    ScrollHandle, Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
-};
-use parking_lot::Mutex;
-use project::{Project, ProjectEntryId, ProjectPath};
-use serde::Deserialize;
-use settings::Settings;
-use std::{
-    any::Any,
-    cmp, fmt, mem,
-    path::{Path, PathBuf},
-    rc::Rc,
-    sync::{
-        atomic::{AtomicUsize, Ordering},
-        Arc,
-    },
-};
-use theme::ThemeSettings;
-
-use ui::{
-    prelude::*, right_click_menu, ButtonSize, Color, Icon, IconButton, IconSize, Indicator, Label,
-    Tab, TabBar, TabPosition, Tooltip,
-};
-use ui::{v_stack, ContextMenu};
-use util::{maybe, truncate_and_remove_front, ResultExt};
-
-#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
-#[serde(rename_all = "camelCase")]
-pub enum SaveIntent {
-    /// write all files (even if unchanged)
-    /// prompt before overwriting on-disk changes
-    Save,
-    /// write any files that have local changes
-    /// prompt before overwriting on-disk changes
-    SaveAll,
-    /// always prompt for a new path
-    SaveAs,
-    /// prompt "you have unsaved changes" before writing
-    Close,
-    /// write all dirty files, don't prompt on conflict
-    Overwrite,
-    /// skip all save-related behavior
-    Skip,
-}
-
-#[derive(Clone, Deserialize, PartialEq, Debug)]
-pub struct ActivateItem(pub usize);
-
-// #[derive(Clone, PartialEq)]
-// pub struct CloseItemById {
-//     pub item_id: usize,
-//     pub pane: WeakView<Pane>,
-// }
-
-// #[derive(Clone, PartialEq)]
-// pub struct CloseItemsToTheLeftById {
-//     pub item_id: usize,
-//     pub pane: WeakView<Pane>,
-// }
-
-// #[derive(Clone, PartialEq)]
-// pub struct CloseItemsToTheRightById {
-//     pub item_id: usize,
-//     pub pane: WeakView<Pane>,
-// }
-
-#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseActiveItem {
-    pub save_intent: Option<SaveIntent>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseAllItems {
-    pub save_intent: Option<SaveIntent>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct RevealInProjectPanel {
-    pub entry_id: u64,
-}
-
-impl_actions!(
-    pane,
-    [
-        CloseAllItems,
-        CloseActiveItem,
-        ActivateItem,
-        RevealInProjectPanel
-    ]
-);
-
-actions!(
-    pane,
-    [
-        ActivatePrevItem,
-        ActivateNextItem,
-        ActivateLastItem,
-        CloseInactiveItems,
-        CloseCleanItems,
-        CloseItemsToTheLeft,
-        CloseItemsToTheRight,
-        GoBack,
-        GoForward,
-        ReopenClosedItem,
-        SplitLeft,
-        SplitUp,
-        SplitRight,
-        SplitDown,
-    ]
-);
-
-const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
-
-pub enum Event {
-    AddItem { item: Box<dyn ItemHandle> },
-    ActivateItem { local: bool },
-    Remove,
-    RemoveItem { item_id: EntityId },
-    Split(SplitDirection),
-    ChangeItemTitle,
-    Focus,
-    ZoomIn,
-    ZoomOut,
-}
-
-impl fmt::Debug for Event {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            Event::AddItem { item } => f
-                .debug_struct("AddItem")
-                .field("item", &item.item_id())
-                .finish(),
-            Event::ActivateItem { local } => f
-                .debug_struct("ActivateItem")
-                .field("local", local)
-                .finish(),
-            Event::Remove => f.write_str("Remove"),
-            Event::RemoveItem { item_id } => f
-                .debug_struct("RemoveItem")
-                .field("item_id", item_id)
-                .finish(),
-            Event::Split(direction) => f
-                .debug_struct("Split")
-                .field("direction", direction)
-                .finish(),
-            Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
-            Event::Focus => f.write_str("Focus"),
-            Event::ZoomIn => f.write_str("ZoomIn"),
-            Event::ZoomOut => f.write_str("ZoomOut"),
-        }
-    }
-}
-
-pub struct Pane {
-    focus_handle: FocusHandle,
-    items: Vec<Box<dyn ItemHandle>>,
-    activation_history: Vec<EntityId>,
-    zoomed: bool,
-    was_focused: bool,
-    active_item_index: usize,
-    last_focused_view_by_item: HashMap<EntityId, FocusHandle>,
-    nav_history: NavHistory,
-    toolbar: View<Toolbar>,
-    new_item_menu: Option<View<ContextMenu>>,
-    split_item_menu: Option<View<ContextMenu>>,
-    //     tab_context_menu: View<ContextMenu>,
-    workspace: WeakView<Workspace>,
-    project: Model<Project>,
-    drag_split_direction: Option<SplitDirection>,
-    can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>>,
-    can_split: bool,
-    render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement>,
-    _subscriptions: Vec<Subscription>,
-    tab_bar_scroll_handle: ScrollHandle,
-    display_nav_history_buttons: bool,
-}
-
-pub struct ItemNavHistory {
-    history: NavHistory,
-    item: Arc<dyn WeakItemHandle>,
-}
-
-#[derive(Clone)]
-pub struct NavHistory(Arc<Mutex<NavHistoryState>>);
-
-struct NavHistoryState {
-    mode: NavigationMode,
-    backward_stack: VecDeque<NavigationEntry>,
-    forward_stack: VecDeque<NavigationEntry>,
-    closed_stack: VecDeque<NavigationEntry>,
-    paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
-    pane: WeakView<Pane>,
-    next_timestamp: Arc<AtomicUsize>,
-}
-
-#[derive(Copy, Clone)]
-pub enum NavigationMode {
-    Normal,
-    GoingBack,
-    GoingForward,
-    ClosingItem,
-    ReopeningClosedItem,
-    Disabled,
-}
-
-impl Default for NavigationMode {
-    fn default() -> Self {
-        Self::Normal
-    }
-}
-
-pub struct NavigationEntry {
-    pub item: Arc<dyn WeakItemHandle>,
-    pub data: Option<Box<dyn Any + Send>>,
-    pub timestamp: usize,
-}
-
-#[derive(Clone)]
-pub struct DraggedTab {
-    pub pane: View<Pane>,
-    pub ix: usize,
-    pub item_id: EntityId,
-    pub detail: usize,
-    pub is_active: bool,
-}
-
-// pub struct DraggedItem {
-//     pub handle: Box<dyn ItemHandle>,
-//     pub pane: WeakView<Pane>,
-// }
-
-// pub enum ReorderBehavior {
-//     None,
-//     MoveAfterActive,
-//     MoveToIndex(usize),
-// }
-
-// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-// enum TabBarContextMenuKind {
-//     New,
-//     Split,
-// }
-
-// struct TabBarContextMenu {
-//     kind: TabBarContextMenuKind,
-//     handle: View<ContextMenu>,
-// }
-
-// impl TabBarContextMenu {
-//     fn handle_if_kind(&self, kind: TabBarContextMenuKind) -> Option<View<ContextMenu>> {
-//         if self.kind == kind {
-//             return Some(self.handle.clone());
-//         }
-//         None
-//     }
-// }
-
-// #[allow(clippy::too_many_arguments)]
-// fn nav_button<A: Action, F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>)>(
-//     svg_path: &'static str,
-//     style: theme2::Interactive<theme2::IconButton>,
-//     nav_button_height: f32,
-//     tooltip_style: TooltipStyle,
-//     enabled: bool,
-//     on_click: F,
-//     tooltip_action: A,
-//     action_name: &str,
-//     cx: &mut ViewContext<Pane>,
-// ) -> AnyElement<Pane> {
-//     MouseEventHandler::new::<A, _>(0, cx, |state, _| {
-//         let style = if enabled {
-//             style.style_for(state)
-//         } else {
-//             style.disabled_style()
-//         };
-//         Svg::new(svg_path)
-//             .with_color(style.color)
-//             .constrained()
-//             .with_width(style.icon_width)
-//             .aligned()
-//             .contained()
-//             .with_style(style.container)
-//             .constrained()
-//             .with_width(style.button_width)
-//             .with_height(nav_button_height)
-//             .aligned()
-//             .top()
-//     })
-//     .with_cursor_style(if enabled {
-//         CursorStyle::PointingHand
-//     } else {
-//         CursorStyle::default()
-//     })
-//     .on_click(MouseButton::Left, move |_, toolbar, cx| {
-//         on_click(toolbar, cx)
-//     })
-//     .with_tooltip::<A>(
-//         0,
-//         action_name.to_string(),
-//         Some(Box::new(tooltip_action)),
-//         tooltip_style,
-//         cx,
-//     )
-//     .contained()
-//     .into_any_named("nav button")
-// }
-
-impl EventEmitter<Event> for Pane {}
-
-impl Pane {
-    pub fn new(
-        workspace: WeakView<Workspace>,
-        project: Model<Project>,
-        next_timestamp: Arc<AtomicUsize>,
-        can_drop_predicate: Option<Arc<dyn Fn(&dyn Any, &mut WindowContext) -> bool + 'static>>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        // todo!("context menu")
-        // let pane_view_id = cx.view_id();
-        // let context_menu = cx.build_view(|cx| ContextMenu::new(pane_view_id, cx));
-        // context_menu.update(cx, |menu, _| {
-        //     menu.set_position_mode(OverlayPositionMode::Local)
-        // });
-        //
-        let focus_handle = cx.focus_handle();
-
-        let subscriptions = vec![
-            cx.on_focus_in(&focus_handle, move |this, cx| this.focus_in(cx)),
-            cx.on_focus_out(&focus_handle, move |this, cx| this.focus_out(cx)),
-        ];
-
-        let handle = cx.view().downgrade();
-        Self {
-            focus_handle,
-            items: Vec::new(),
-            activation_history: Vec::new(),
-            was_focused: false,
-            zoomed: false,
-            active_item_index: 0,
-            last_focused_view_by_item: Default::default(),
-            nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
-                mode: NavigationMode::Normal,
-                backward_stack: Default::default(),
-                forward_stack: Default::default(),
-                closed_stack: Default::default(),
-                paths_by_item: Default::default(),
-                pane: handle.clone(),
-                next_timestamp,
-            }))),
-            toolbar: cx.new_view(|_| Toolbar::new()),
-            new_item_menu: None,
-            split_item_menu: None,
-            tab_bar_scroll_handle: ScrollHandle::new(),
-            drag_split_direction: None,
-            // tab_bar_context_menu: TabBarContextMenu {
-            //     kind: TabBarContextMenuKind::New,
-            //     handle: context_menu,
-            // },
-            // tab_context_menu: cx.build_view(|_| ContextMenu::new(pane_view_id, cx)),
-            workspace,
-            project,
-            can_drop_predicate,
-            can_split: true,
-            render_tab_bar_buttons: Rc::new(move |pane, cx| {
-                h_stack()
-                    .gap_2()
-                    .child(
-                        IconButton::new("plus", Icon::Plus)
-                            .icon_size(IconSize::Small)
-                            .icon_color(Color::Muted)
-                            .on_click(cx.listener(|pane, _, cx| {
-                                let menu = ContextMenu::build(cx, |menu, _| {
-                                    menu.action("New File", NewFile.boxed_clone())
-                                        .action("New Terminal", NewCenterTerminal.boxed_clone())
-                                        .action("New Search", NewSearch.boxed_clone())
-                                });
-                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
-                                    pane.focus(cx);
-                                    pane.new_item_menu = None;
-                                })
-                                .detach();
-                                pane.new_item_menu = Some(menu);
-                            }))
-                            .tooltip(|cx| Tooltip::text("New...", cx)),
-                    )
-                    .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
-                        el.child(Self::render_menu_overlay(new_item_menu))
-                    })
-                    .child(
-                        IconButton::new("split", Icon::Split)
-                            .icon_size(IconSize::Small)
-                            .icon_color(Color::Muted)
-                            .on_click(cx.listener(|pane, _, cx| {
-                                let menu = ContextMenu::build(cx, |menu, _| {
-                                    menu.action("Split Right", SplitRight.boxed_clone())
-                                        .action("Split Left", SplitLeft.boxed_clone())
-                                        .action("Split Up", SplitUp.boxed_clone())
-                                        .action("Split Down", SplitDown.boxed_clone())
-                                });
-                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, cx| {
-                                    pane.focus(cx);
-                                    pane.split_item_menu = None;
-                                })
-                                .detach();
-                                pane.split_item_menu = Some(menu);
-                            }))
-                            .tooltip(|cx| Tooltip::text("Split Pane", cx)),
-                    )
-                    .child({
-                        let zoomed = pane.is_zoomed();
-                        IconButton::new("toggle_zoom", Icon::Maximize)
-                            .icon_size(IconSize::Small)
-                            .icon_color(Color::Muted)
-                            .selected(zoomed)
-                            .selected_icon(Icon::Minimize)
-                            .on_click(cx.listener(|pane, _, cx| {
-                                pane.toggle_zoom(&crate::ToggleZoom, cx);
-                            }))
-                            .tooltip(move |cx| {
-                                Tooltip::text(if zoomed { "Zoom Out" } else { "Zoom In" }, cx)
-                            })
-                    })
-                    .when_some(pane.split_item_menu.as_ref(), |el, split_item_menu| {
-                        el.child(Self::render_menu_overlay(split_item_menu))
-                    })
-                    .into_any_element()
-            }),
-            display_nav_history_buttons: true,
-            _subscriptions: subscriptions,
-        }
-    }
-
-    pub fn has_focus(&self, cx: &WindowContext) -> bool {
-        // todo!(); // inline this manually
-        self.focus_handle.contains_focused(cx)
-    }
-
-    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
-        if !self.was_focused {
-            self.was_focused = true;
-            cx.emit(Event::Focus);
-            cx.notify();
-        }
-
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.focus_changed(true, cx);
-        });
-
-        if let Some(active_item) = self.active_item() {
-            if self.focus_handle.is_focused(cx) {
-                // Pane was focused directly. We need to either focus a view inside the active item,
-                // or focus the active item itself
-                if let Some(weak_last_focused_view) =
-                    self.last_focused_view_by_item.get(&active_item.item_id())
-                {
-                    weak_last_focused_view.focus(cx);
-                    return;
-                }
-
-                active_item.focus_handle(cx).focus(cx);
-            } else if let Some(focused) = cx.focused() {
-                if !self.context_menu_focused(cx) {
-                    self.last_focused_view_by_item
-                        .insert(active_item.item_id(), focused);
-                }
-            }
-        }
-    }
-
-    fn context_menu_focused(&self, cx: &mut ViewContext<Self>) -> bool {
-        self.new_item_menu
-            .as_ref()
-            .or(self.split_item_menu.as_ref())
-            .map_or(false, |menu| menu.focus_handle(cx).is_focused(cx))
-    }
-
-    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
-        self.was_focused = false;
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.focus_changed(false, cx);
-        });
-        cx.notify();
-    }
-
-    pub fn active_item_index(&self) -> usize {
-        self.active_item_index
-    }
-
-    //     pub fn on_can_drop<F>(&mut self, can_drop: F)
-    //     where
-    //         F: 'static + Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool,
-    //     {
-    //         self.can_drop = Rc::new(can_drop);
-    //     }
-
-    pub fn set_can_split(&mut self, can_split: bool, cx: &mut ViewContext<Self>) {
-        self.can_split = can_split;
-        cx.notify();
-    }
-
-    pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.set_can_navigate(can_navigate, cx);
-        });
-        cx.notify();
-    }
-
-    pub fn set_render_tab_bar_buttons<F>(&mut self, cx: &mut ViewContext<Self>, render: F)
-    where
-        F: 'static + Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement,
-    {
-        self.render_tab_bar_buttons = Rc::new(render);
-        cx.notify();
-    }
-
-    pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
-        ItemNavHistory {
-            history: self.nav_history.clone(),
-            item: Arc::new(item.downgrade()),
-        }
-    }
-
-    pub fn nav_history(&self) -> &NavHistory {
-        &self.nav_history
-    }
-
-    pub fn nav_history_mut(&mut self) -> &mut NavHistory {
-        &mut self.nav_history
-    }
-
-    pub fn disable_history(&mut self) {
-        self.nav_history.disable();
-    }
-
-    pub fn enable_history(&mut self) {
-        self.nav_history.enable();
-    }
-
-    pub fn can_navigate_backward(&self) -> bool {
-        !self.nav_history.0.lock().backward_stack.is_empty()
-    }
-
-    pub fn can_navigate_forward(&self) -> bool {
-        !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());
-    }
-
-    pub(crate) fn open_item(
-        &mut self,
-        project_entry_id: Option<ProjectEntryId>,
-        focus_item: bool,
-        cx: &mut ViewContext<Self>,
-        build_item: impl FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
-    ) -> Box<dyn ItemHandle> {
-        let mut existing_item = None;
-        if let Some(project_entry_id) = project_entry_id {
-            for (index, item) in self.items.iter().enumerate() {
-                if item.is_singleton(cx)
-                    && item.project_entry_ids(cx).as_slice() == [project_entry_id]
-                {
-                    let item = item.boxed_clone();
-                    existing_item = Some((index, item));
-                    break;
-                }
-            }
-        }
-
-        if let Some((index, existing_item)) = existing_item {
-            self.activate_item(index, focus_item, focus_item, cx);
-            existing_item
-        } else {
-            let new_item = build_item(cx);
-            self.add_item(new_item.clone(), true, focus_item, None, cx);
-            new_item
-        }
-    }
-
-    pub fn add_item(
-        &mut self,
-        item: Box<dyn ItemHandle>,
-        activate_pane: bool,
-        focus_item: bool,
-        destination_index: Option<usize>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if item.is_singleton(cx) {
-            if let Some(&entry_id) = item.project_entry_ids(cx).get(0) {
-                let project = self.project.read(cx);
-                if let Some(project_path) = project.path_for_entry(entry_id, cx) {
-                    let abs_path = project.absolute_path(&project_path, cx);
-                    self.nav_history
-                        .0
-                        .lock()
-                        .paths_by_item
-                        .insert(item.item_id(), (project_path, abs_path));
-                }
-            }
-        }
-        // If no destination index is specified, add or move the item after the active item.
-        let mut insertion_index = {
-            cmp::min(
-                if let Some(destination_index) = destination_index {
-                    destination_index
-                } else {
-                    self.active_item_index + 1
-                },
-                self.items.len(),
-            )
-        };
-
-        // Does the item already exist?
-        let project_entry_id = if item.is_singleton(cx) {
-            item.project_entry_ids(cx).get(0).copied()
-        } else {
-            None
-        };
-
-        let existing_item_index = self.items.iter().position(|existing_item| {
-            if existing_item.item_id() == item.item_id() {
-                true
-            } else if existing_item.is_singleton(cx) {
-                existing_item
-                    .project_entry_ids(cx)
-                    .get(0)
-                    .map_or(false, |existing_entry_id| {
-                        Some(existing_entry_id) == project_entry_id.as_ref()
-                    })
-            } else {
-                false
-            }
-        });
-
-        if let Some(existing_item_index) = existing_item_index {
-            // If the item already exists, move it to the desired destination and activate it
-
-            if existing_item_index != insertion_index {
-                let existing_item_is_active = existing_item_index == self.active_item_index;
-
-                // If the caller didn't specify a destination and the added item is already
-                // the active one, don't move it
-                if existing_item_is_active && destination_index.is_none() {
-                    insertion_index = existing_item_index;
-                } else {
-                    self.items.remove(existing_item_index);
-                    if existing_item_index < self.active_item_index {
-                        self.active_item_index -= 1;
-                    }
-                    insertion_index = insertion_index.min(self.items.len());
-
-                    self.items.insert(insertion_index, item.clone());
-
-                    if existing_item_is_active {
-                        self.active_item_index = insertion_index;
-                    } else if insertion_index <= self.active_item_index {
-                        self.active_item_index += 1;
-                    }
-                }
-
-                cx.notify();
-            }
-
-            self.activate_item(insertion_index, activate_pane, focus_item, cx);
-        } else {
-            self.items.insert(insertion_index, item.clone());
-            if insertion_index <= self.active_item_index {
-                self.active_item_index += 1;
-            }
-
-            self.activate_item(insertion_index, activate_pane, focus_item, cx);
-            cx.notify();
-        }
-
-        cx.emit(Event::AddItem { item });
-    }
-
-    pub fn items_len(&self) -> usize {
-        self.items.len()
-    }
-
-    pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
-        self.items.iter()
-    }
-
-    pub fn items_of_type<T: Render>(&self) -> impl '_ + Iterator<Item = View<T>> {
-        self.items
-            .iter()
-            .filter_map(|item| item.to_any().downcast().ok())
-    }
-
-    pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
-        self.items.get(self.active_item_index).cloned()
-    }
-
-    pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
-        self.items
-            .get(self.active_item_index)?
-            .pixel_position_of_cursor(cx)
-    }
-
-    pub fn item_for_entry(
-        &self,
-        entry_id: ProjectEntryId,
-        cx: &AppContext,
-    ) -> Option<Box<dyn ItemHandle>> {
-        self.items.iter().find_map(|item| {
-            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
-                Some(item.boxed_clone())
-            } else {
-                None
-            }
-        })
-    }
-
-    pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
-        self.items
-            .iter()
-            .position(|i| i.item_id() == item.item_id())
-    }
-
-    pub fn item_for_index(&self, ix: usize) -> Option<&dyn ItemHandle> {
-        self.items.get(ix).map(|i| i.as_ref())
-    }
-
-    pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
-        if self.zoomed {
-            cx.emit(Event::ZoomOut);
-        } else if !self.items.is_empty() {
-            if !self.focus_handle.contains_focused(cx) {
-                cx.focus_self();
-            }
-            cx.emit(Event::ZoomIn);
-        }
-    }
-
-    pub fn activate_item(
-        &mut self,
-        index: usize,
-        activate_pane: bool,
-        focus_item: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        use NavigationMode::{GoingBack, GoingForward};
-
-        if index < self.items.len() {
-            let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
-            if prev_active_item_ix != self.active_item_index
-                || matches!(self.nav_history.mode(), GoingBack | GoingForward)
-            {
-                if let Some(prev_item) = self.items.get(prev_active_item_ix) {
-                    prev_item.deactivated(cx);
-                }
-
-                cx.emit(Event::ActivateItem {
-                    local: activate_pane,
-                });
-            }
-
-            if let Some(newly_active_item) = self.items.get(index) {
-                self.activation_history
-                    .retain(|&previously_active_item_id| {
-                        previously_active_item_id != newly_active_item.item_id()
-                    });
-                self.activation_history.push(newly_active_item.item_id());
-            }
-
-            self.update_toolbar(cx);
-            self.update_status_bar(cx);
-
-            if focus_item {
-                self.focus_active_item(cx);
-            }
-
-            self.tab_bar_scroll_handle.scroll_to_item(index);
-            cx.notify();
-        }
-    }
-
-    pub fn activate_prev_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
-        let mut index = self.active_item_index;
-        if index > 0 {
-            index -= 1;
-        } else if !self.items.is_empty() {
-            index = self.items.len() - 1;
-        }
-        self.activate_item(index, activate_pane, activate_pane, cx);
-    }
-
-    pub fn activate_next_item(&mut self, activate_pane: bool, cx: &mut ViewContext<Self>) {
-        let mut index = self.active_item_index;
-        if index + 1 < self.items.len() {
-            index += 1;
-        } else {
-            index = 0;
-        }
-        self.activate_item(index, activate_pane, activate_pane, cx);
-    }
-
-    pub fn close_active_item(
-        &mut self,
-        action: &CloseActiveItem,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-        let active_item_id = self.items[self.active_item_index].item_id();
-        Some(self.close_item_by_id(
-            active_item_id,
-            action.save_intent.unwrap_or(SaveIntent::Close),
-            cx,
-        ))
-    }
-
-    pub fn close_item_by_id(
-        &mut self,
-        item_id_to_close: EntityId,
-        save_intent: SaveIntent,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
-    }
-
-    pub fn close_inactive_items(
-        &mut self,
-        _: &CloseInactiveItems,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-
-        let active_item_id = self.items[self.active_item_index].item_id();
-        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_id != active_item_id
-        }))
-    }
-
-    pub fn close_clean_items(
-        &mut self,
-        _: &CloseCleanItems,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let item_ids: Vec<_> = self
-            .items()
-            .filter(|item| !item.is_dirty(cx))
-            .map(|item| item.item_id())
-            .collect();
-        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_ids.contains(&item_id)
-        }))
-    }
-
-    pub fn close_items_to_the_left(
-        &mut self,
-        _: &CloseItemsToTheLeft,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-        let active_item_id = self.items[self.active_item_index].item_id();
-        Some(self.close_items_to_the_left_by_id(active_item_id, cx))
-    }
-
-    pub fn close_items_to_the_left_by_id(
-        &mut self,
-        item_id: EntityId,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let item_ids: Vec<_> = self
-            .items()
-            .take_while(|item| item.item_id() != item_id)
-            .map(|item| item.item_id())
-            .collect();
-        self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_ids.contains(&item_id)
-        })
-    }
-
-    pub fn close_items_to_the_right(
-        &mut self,
-        _: &CloseItemsToTheRight,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-        let active_item_id = self.items[self.active_item_index].item_id();
-        Some(self.close_items_to_the_right_by_id(active_item_id, cx))
-    }
-
-    pub fn close_items_to_the_right_by_id(
-        &mut self,
-        item_id: EntityId,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let item_ids: Vec<_> = self
-            .items()
-            .rev()
-            .take_while(|item| item.item_id() != item_id)
-            .map(|item| item.item_id())
-            .collect();
-        self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_ids.contains(&item_id)
-        })
-    }
-
-    pub fn close_all_items(
-        &mut self,
-        action: &CloseAllItems,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        if self.items.is_empty() {
-            return None;
-        }
-
-        Some(
-            self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
-                true
-            }),
-        )
-    }
-
-    pub(super) fn file_names_for_prompt(
-        items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
-        all_dirty_items: usize,
-        cx: &AppContext,
-    ) -> String {
-        /// Quantity of item paths displayed in prompt prior to cutoff..
-        const FILE_NAMES_CUTOFF_POINT: usize = 10;
-        let mut file_names: Vec<_> = items
-            .filter_map(|item| {
-                item.project_path(cx).and_then(|project_path| {
-                    project_path
-                        .path
-                        .file_name()
-                        .and_then(|name| name.to_str().map(ToOwned::to_owned))
-                })
-            })
-            .take(FILE_NAMES_CUTOFF_POINT)
-            .collect();
-        let should_display_followup_text =
-            all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
-        if should_display_followup_text {
-            let not_shown_files = all_dirty_items - file_names.len();
-            if not_shown_files == 1 {
-                file_names.push(".. 1 file not shown".into());
-            } else {
-                file_names.push(format!(".. {} files not shown", not_shown_files).into());
-            }
-        }
-        let file_names = file_names.join("\n");
-        format!(
-            "Do you want to save changes to the following {} files?\n{file_names}",
-            all_dirty_items
-        )
-    }
-
-    pub fn close_items(
-        &mut self,
-        cx: &mut ViewContext<Pane>,
-        mut save_intent: SaveIntent,
-        should_close: impl Fn(EntityId) -> bool,
-    ) -> Task<Result<()>> {
-        // Find the items to close.
-        let mut items_to_close = Vec::new();
-        let mut dirty_items = Vec::new();
-        for item in &self.items {
-            if should_close(item.item_id()) {
-                items_to_close.push(item.boxed_clone());
-                if item.is_dirty(cx) {
-                    dirty_items.push(item.boxed_clone());
-                }
-            }
-        }
-
-        // If a buffer is open both in a singleton editor and in a multibuffer, make sure
-        // to focus the singleton buffer when prompting to save that buffer, as opposed
-        // to focusing the multibuffer, because this gives the user a more clear idea
-        // of what content they would be saving.
-        items_to_close.sort_by_key(|item| !item.is_singleton(cx));
-
-        let workspace = self.workspace.clone();
-        cx.spawn(|pane, mut cx| async move {
-            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-                let answer = pane.update(&mut cx, |_, cx| {
-                    let prompt =
-                        Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
-                    cx.prompt(
-                        PromptLevel::Warning,
-                        &prompt,
-                        &["Save all", "Discard all", "Cancel"],
-                    )
-                })?;
-                match answer.await {
-                    Ok(0) => save_intent = SaveIntent::SaveAll,
-                    Ok(1) => save_intent = SaveIntent::Skip,
-                    _ => {}
-                }
-            }
-            let mut saved_project_items_ids = HashSet::default();
-            for item in items_to_close.clone() {
-                // Find the item's current index and its set of project item models. Avoid
-                // storing these in advance, in case they have changed since this task
-                // was started.
-                let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
-                    (pane.index_for_item(&*item), item.project_item_model_ids(cx))
-                })?;
-                let item_ix = if let Some(ix) = item_ix {
-                    ix
-                } else {
-                    continue;
-                };
-
-                // Check if this view has any project items that are not open anywhere else
-                // in the workspace, AND that the user has not already been prompted to save.
-                // If there are any such project entries, prompt the user to save this item.
-                let project = workspace.update(&mut cx, |workspace, cx| {
-                    for item in workspace.items(cx) {
-                        if !items_to_close
-                            .iter()
-                            .any(|item_to_close| item_to_close.item_id() == item.item_id())
-                        {
-                            let other_project_item_ids = item.project_item_model_ids(cx);
-                            project_item_ids.retain(|id| !other_project_item_ids.contains(id));
-                        }
-                    }
-                    workspace.project().clone()
-                })?;
-                let should_save = project_item_ids
-                    .iter()
-                    .any(|id| saved_project_items_ids.insert(*id));
-
-                if should_save
-                    && !Self::save_item(
-                        project.clone(),
-                        &pane,
-                        item_ix,
-                        &*item,
-                        save_intent,
-                        &mut cx,
-                    )
-                    .await?
-                {
-                    break;
-                }
-
-                // Remove the item from the pane.
-                pane.update(&mut cx, |pane, cx| {
-                    if let Some(item_ix) = pane
-                        .items
-                        .iter()
-                        .position(|i| i.item_id() == item.item_id())
-                    {
-                        pane.remove_item(item_ix, false, cx);
-                    }
-                })
-                .ok();
-            }
-
-            pane.update(&mut cx, |_, cx| cx.notify()).ok();
-            Ok(())
-        })
-    }
-
-    pub fn remove_item(
-        &mut self,
-        item_index: usize,
-        activate_pane: bool,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.activation_history
-            .retain(|&history_entry| history_entry != self.items[item_index].item_id());
-
-        if item_index == self.active_item_index {
-            let index_to_activate = self
-                .activation_history
-                .pop()
-                .and_then(|last_activated_item| {
-                    self.items.iter().enumerate().find_map(|(index, item)| {
-                        (item.item_id() == last_activated_item).then_some(index)
-                    })
-                })
-                // We didn't have a valid activation history entry, so fallback
-                // to activating the item to the left
-                .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
-
-            let should_activate = activate_pane || self.has_focus(cx);
-            if self.items.len() == 1 && should_activate {
-                self.focus_handle.focus(cx);
-            } else {
-                self.activate_item(index_to_activate, should_activate, should_activate, cx);
-            }
-        }
-
-        let item = self.items.remove(item_index);
-
-        cx.emit(Event::RemoveItem {
-            item_id: item.item_id(),
-        });
-        if self.items.is_empty() {
-            item.deactivated(cx);
-            self.update_toolbar(cx);
-            cx.emit(Event::Remove);
-        }
-
-        if item_index < self.active_item_index {
-            self.active_item_index -= 1;
-        }
-
-        self.nav_history.set_mode(NavigationMode::ClosingItem);
-        item.deactivated(cx);
-        self.nav_history.set_mode(NavigationMode::Normal);
-
-        if let Some(path) = item.project_path(cx) {
-            let abs_path = self
-                .nav_history
-                .0
-                .lock()
-                .paths_by_item
-                .get(&item.item_id())
-                .and_then(|(_, abs_path)| abs_path.clone());
-
-            self.nav_history
-                .0
-                .lock()
-                .paths_by_item
-                .insert(item.item_id(), (path, abs_path));
-        } else {
-            self.nav_history
-                .0
-                .lock()
-                .paths_by_item
-                .remove(&item.item_id());
-        }
-
-        if self.items.is_empty() && self.zoomed {
-            cx.emit(Event::ZoomOut);
-        }
-
-        cx.notify();
-    }
-
-    pub async fn save_item(
-        project: Model<Project>,
-        pane: &WeakView<Pane>,
-        item_ix: usize,
-        item: &dyn ItemHandle,
-        save_intent: SaveIntent,
-        cx: &mut AsyncWindowContext,
-    ) -> Result<bool> {
-        const CONFLICT_MESSAGE: &str =
-                "This file has changed on disk since you started editing it. Do you want to overwrite it?";
-
-        if save_intent == SaveIntent::Skip {
-            return Ok(true);
-        }
-
-        let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
-            (
-                item.has_conflict(cx),
-                item.is_dirty(cx),
-                item.can_save(cx),
-                item.is_singleton(cx),
-            )
-        })?;
-
-        // when saving a single buffer, we ignore whether or not it's dirty.
-        if save_intent == SaveIntent::Save {
-            is_dirty = true;
-        }
-
-        if save_intent == SaveIntent::SaveAs {
-            is_dirty = true;
-            has_conflict = false;
-            can_save = false;
-        }
-
-        if save_intent == SaveIntent::Overwrite {
-            has_conflict = false;
-        }
-
-        if has_conflict && can_save {
-            let answer = pane.update(cx, |pane, cx| {
-                pane.activate_item(item_ix, true, true, cx);
-                cx.prompt(
-                    PromptLevel::Warning,
-                    CONFLICT_MESSAGE,
-                    &["Overwrite", "Discard", "Cancel"],
-                )
-            })?;
-            match answer.await {
-                Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
-                Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
-                _ => return Ok(false),
-            }
-        } else if is_dirty && (can_save || can_save_as) {
-            if save_intent == SaveIntent::Close {
-                let will_autosave = cx.update(|_, cx| {
-                    matches!(
-                        WorkspaceSettings::get_global(cx).autosave,
-                        AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
-                    ) && Self::can_autosave_item(&*item, cx)
-                })?;
-                if !will_autosave {
-                    let answer = pane.update(cx, |pane, cx| {
-                        pane.activate_item(item_ix, true, true, cx);
-                        let prompt = dirty_message_for(item.project_path(cx));
-                        cx.prompt(
-                            PromptLevel::Warning,
-                            &prompt,
-                            &["Save", "Don't Save", "Cancel"],
-                        )
-                    })?;
-                    match answer.await {
-                        Ok(0) => {}
-                        Ok(1) => return Ok(true), // Don't save this file
-                        _ => return Ok(false),    // Cancel
-                    }
-                }
-            }
-
-            if can_save {
-                pane.update(cx, |_, cx| item.save(project, cx))?.await?;
-            } else if can_save_as {
-                let start_abs_path = project
-                    .update(cx, |project, cx| {
-                        let worktree = project.visible_worktrees(cx).next()?;
-                        Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
-                    })?
-                    .unwrap_or_else(|| Path::new("").into());
-
-                let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
-                if let Some(abs_path) = abs_path.await.ok().flatten() {
-                    pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
-                        .await?;
-                } else {
-                    return Ok(false);
-                }
-            }
-        }
-        Ok(true)
-    }
-
-    fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
-        let is_deleted = item.project_entry_ids(cx).is_empty();
-        item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
-    }
-
-    pub fn autosave_item(
-        item: &dyn ItemHandle,
-        project: Model<Project>,
-        cx: &mut WindowContext,
-    ) -> Task<Result<()>> {
-        if Self::can_autosave_item(item, cx) {
-            item.save(project, cx)
-        } else {
-            Task::ready(Ok(()))
-        }
-    }
-
-    pub fn focus(&mut self, cx: &mut ViewContext<Pane>) {
-        cx.focus(&self.focus_handle);
-    }
-
-    pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(active_item) = self.active_item() {
-            let focus_handle = active_item.focus_handle(cx);
-            cx.focus(&focus_handle);
-        }
-    }
-
-    pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
-        cx.emit(Event::Split(direction));
-    }
-
-    //     fn deploy_split_menu(&mut self, cx: &mut ViewContext<Self>) {
-    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
-    //             menu.toggle(
-    //                 Default::default(),
-    //                 AnchorCorner::TopRight,
-    //                 vec![
-    //                     ContextMenuItem::action("Split Right", SplitRight),
-    //                     ContextMenuItem::action("Split Left", SplitLeft),
-    //                     ContextMenuItem::action("Split Up", SplitUp),
-    //                     ContextMenuItem::action("Split Down", SplitDown),
-    //                 ],
-    //                 cx,
-    //             );
-    //         });
-
-    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::Split;
-    //     }
-
-    //     fn deploy_new_menu(&mut self, cx: &mut ViewContext<Self>) {
-    //         self.tab_bar_context_menu.handle.update(cx, |menu, cx| {
-    //             menu.toggle(
-    //                 Default::default(),
-    //                 AnchorCorner::TopRight,
-    //                 vec![
-    //                     ContextMenuItem::action("New File", NewFile),
-    //                     ContextMenuItem::action("New Terminal", NewCenterTerminal),
-    //                     ContextMenuItem::action("New Search", NewSearch),
-    //                 ],
-    //                 cx,
-    //             );
-    //         });
-
-    //         self.tab_bar_context_menu.kind = TabBarContextMenuKind::New;
-    //     }
-
-    //     fn deploy_tab_context_menu(
-    //         &mut self,
-    //         position: Vector2F,
-    //         target_item_id: usize,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         let active_item_id = self.items[self.active_item_index].id();
-    //         let is_active_item = target_item_id == active_item_id;
-    //         let target_pane = cx.weak_handle();
-
-    //         // The `CloseInactiveItems` action should really be called "CloseOthers" and the behaviour should be dynamically based on the tab the action is ran on.  Currently, this is a weird action because you can run it on a non-active tab and it will close everything by the actual active tab
-
-    //         self.tab_context_menu.update(cx, |menu, cx| {
-    //             menu.show(
-    //                 position,
-    //                 AnchorCorner::TopLeft,
-    //                 if is_active_item {
-    //                     vec![
-    //                         ContextMenuItem::action(
-    //                             "Close Active Item",
-    //                             CloseActiveItem { save_intent: None },
-    //                         ),
-    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
-    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
-    //                         ContextMenuItem::action("Close Items To The Left", CloseItemsToTheLeft),
-    //                         ContextMenuItem::action("Close Items To The Right", CloseItemsToTheRight),
-    //                         ContextMenuItem::action(
-    //                             "Close All Items",
-    //                             CloseAllItems { save_intent: None },
-    //                         ),
-    //                     ]
-    //                 } else {
-    //                     // In the case of the user right clicking on a non-active tab, for some item-closing commands, we need to provide the id of the tab, for the others, we can reuse the existing command.
-    //                     vec![
-    //                         ContextMenuItem::handler("Close Inactive Item", {
-    //                             let pane = target_pane.clone();
-    //                             move |cx| {
-    //                                 if let Some(pane) = pane.upgrade(cx) {
-    //                                     pane.update(cx, |pane, cx| {
-    //                                         pane.close_item_by_id(
-    //                                             target_item_id,
-    //                                             SaveIntent::Close,
-    //                                             cx,
-    //                                         )
-    //                                         .detach_and_log_err(cx);
-    //                                     })
-    //                                 }
-    //                             }
-    //                         }),
-    //                         ContextMenuItem::action("Close Inactive Items", CloseInactiveItems),
-    //                         ContextMenuItem::action("Close Clean Items", CloseCleanItems),
-    //                         ContextMenuItem::handler("Close Items To The Left", {
-    //                             let pane = target_pane.clone();
-    //                             move |cx| {
-    //                                 if let Some(pane) = pane.upgrade(cx) {
-    //                                     pane.update(cx, |pane, cx| {
-    //                                         pane.close_items_to_the_left_by_id(target_item_id, cx)
-    //                                             .detach_and_log_err(cx);
-    //                                     })
-    //                                 }
-    //                             }
-    //                         }),
-    //                         ContextMenuItem::handler("Close Items To The Right", {
-    //                             let pane = target_pane.clone();
-    //                             move |cx| {
-    //                                 if let Some(pane) = pane.upgrade(cx) {
-    //                                     pane.update(cx, |pane, cx| {
-    //                                         pane.close_items_to_the_right_by_id(target_item_id, cx)
-    //                                             .detach_and_log_err(cx);
-    //                                     })
-    //                                 }
-    //                             }
-    //                         }),
-    //                         ContextMenuItem::action(
-    //                             "Close All Items",
-    //                             CloseAllItems { save_intent: None },
-    //                         ),
-    //                     ]
-    //                 },
-    //                 cx,
-    //             );
-    //         });
-    //     }
-
-    pub fn toolbar(&self) -> &View<Toolbar> {
-        &self.toolbar
-    }
-
-    pub fn handle_deleted_project_item(
-        &mut self,
-        entry_id: ProjectEntryId,
-        cx: &mut ViewContext<Pane>,
-    ) -> Option<()> {
-        let (item_index_to_delete, item_id) = self.items().enumerate().find_map(|(i, item)| {
-            if item.is_singleton(cx) && item.project_entry_ids(cx).as_slice() == [entry_id] {
-                Some((i, item.item_id()))
-            } else {
-                None
-            }
-        })?;
-
-        self.remove_item(item_index_to_delete, false, cx);
-        self.nav_history.remove_item(item_id);
-
-        Some(())
-    }
-
-    fn update_toolbar(&mut self, cx: &mut ViewContext<Self>) {
-        let active_item = self
-            .items
-            .get(self.active_item_index)
-            .map(|item| item.as_ref());
-        self.toolbar.update(cx, |toolbar, cx| {
-            toolbar.set_active_item(active_item, cx);
-        });
-    }
-
-    fn update_status_bar(&mut self, cx: &mut ViewContext<Self>) {
-        let workspace = self.workspace.clone();
-        let pane = cx.view().clone();
-
-        cx.window_context().defer(move |cx| {
-            let Ok(status_bar) = workspace.update(cx, |workspace, _| workspace.status_bar.clone())
-            else {
-                return;
-            };
-
-            status_bar.update(cx, move |status_bar, cx| {
-                status_bar.set_active_pane(&pane, cx);
-            });
-        });
-    }
-
-    fn render_tab(
-        &self,
-        ix: usize,
-        item: &Box<dyn ItemHandle>,
-        detail: usize,
-        cx: &mut ViewContext<'_, Pane>,
-    ) -> impl IntoElement {
-        let is_active = ix == self.active_item_index;
-
-        let label = item.tab_content(Some(detail), is_active, cx);
-        let close_side = &ItemSettings::get_global(cx).close_position;
-
-        let indicator = maybe!({
-            let indicator_color = match (item.has_conflict(cx), item.is_dirty(cx)) {
-                (true, _) => Color::Warning,
-                (_, true) => Color::Accent,
-                (false, false) => return None,
-            };
-
-            Some(Indicator::dot().color(indicator_color))
-        });
-
-        let item_id = item.item_id();
-        let is_first_item = ix == 0;
-        let is_last_item = ix == self.items.len() - 1;
-        let position_relative_to_active_item = ix.cmp(&self.active_item_index);
-
-        let tab = Tab::new(ix)
-            .position(if is_first_item {
-                TabPosition::First
-            } else if is_last_item {
-                TabPosition::Last
-            } else {
-                TabPosition::Middle(position_relative_to_active_item)
-            })
-            .close_side(match close_side {
-                ClosePosition::Left => ui::TabCloseSide::Start,
-                ClosePosition::Right => ui::TabCloseSide::End,
-            })
-            .selected(is_active)
-            .on_click(
-                cx.listener(move |pane: &mut Self, _, cx| pane.activate_item(ix, true, true, cx)),
-            )
-            // TODO: This should be a click listener with the middle mouse button instead of a mouse down listener.
-            .on_mouse_down(
-                MouseButton::Middle,
-                cx.listener(move |pane, _event, cx| {
-                    pane.close_item_by_id(item_id, SaveIntent::Close, cx)
-                        .detach_and_log_err(cx);
-                }),
-            )
-            .on_drag(
-                DraggedTab {
-                    pane: cx.view().clone(),
-                    detail,
-                    item_id,
-                    is_active,
-                    ix,
-                },
-                |tab, cx| cx.new_view(|_| tab.clone()),
-            )
-            .drag_over::<DraggedTab>(|tab| tab.bg(cx.theme().colors().drop_target_background))
-            .drag_over::<ProjectEntryId>(|tab| tab.bg(cx.theme().colors().drop_target_background))
-            .when_some(self.can_drop_predicate.clone(), |this, p| {
-                this.can_drop(move |a, cx| p(a, cx))
-            })
-            .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
-                this.drag_split_direction = None;
-                this.handle_tab_drop(dragged_tab, ix, cx)
-            }))
-            .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
-                this.drag_split_direction = None;
-                this.handle_project_entry_drop(entry_id, cx)
-            }))
-            .when_some(item.tab_tooltip_text(cx), |tab, text| {
-                tab.tooltip(move |cx| Tooltip::text(text.clone(), cx))
-            })
-            .start_slot::<Indicator>(indicator)
-            .end_slot(
-                IconButton::new("close tab", Icon::Close)
-                    .icon_color(Color::Muted)
-                    .size(ButtonSize::None)
-                    .icon_size(IconSize::XSmall)
-                    .on_click(cx.listener(move |pane, _, cx| {
-                        pane.close_item_by_id(item_id, SaveIntent::Close, cx)
-                            .detach_and_log_err(cx);
-                    })),
-            )
-            .child(label);
-
-        let single_entry_to_resolve = {
-            let item_entries = self.items[ix].project_entry_ids(cx);
-            if item_entries.len() == 1 {
-                Some(item_entries[0])
-            } else {
-                None
-            }
-        };
-
-        let pane = cx.view().downgrade();
-        right_click_menu(ix).trigger(tab).menu(move |cx| {
-            let pane = pane.clone();
-            ContextMenu::build(cx, move |mut menu, cx| {
-                if let Some(pane) = pane.upgrade() {
-                    menu = menu
-                        .entry(
-                            "Close",
-                            Some(Box::new(CloseActiveItem { save_intent: None })),
-                            cx.handler_for(&pane, move |pane, cx| {
-                                pane.close_item_by_id(item_id, SaveIntent::Close, cx)
-                                    .detach_and_log_err(cx);
-                            }),
-                        )
-                        .entry(
-                            "Close Others",
-                            Some(Box::new(CloseInactiveItems)),
-                            cx.handler_for(&pane, move |pane, cx| {
-                                pane.close_items(cx, SaveIntent::Close, |id| id != item_id)
-                                    .detach_and_log_err(cx);
-                            }),
-                        )
-                        .separator()
-                        .entry(
-                            "Close Left",
-                            Some(Box::new(CloseItemsToTheLeft)),
-                            cx.handler_for(&pane, move |pane, cx| {
-                                pane.close_items_to_the_left_by_id(item_id, cx)
-                                    .detach_and_log_err(cx);
-                            }),
-                        )
-                        .entry(
-                            "Close Right",
-                            Some(Box::new(CloseItemsToTheRight)),
-                            cx.handler_for(&pane, move |pane, cx| {
-                                pane.close_items_to_the_right_by_id(item_id, cx)
-                                    .detach_and_log_err(cx);
-                            }),
-                        )
-                        .separator()
-                        .entry(
-                            "Close Clean",
-                            Some(Box::new(CloseCleanItems)),
-                            cx.handler_for(&pane, move |pane, cx| {
-                                pane.close_clean_items(&CloseCleanItems, cx)
-                                    .map(|task| task.detach_and_log_err(cx));
-                            }),
-                        )
-                        .entry(
-                            "Close All",
-                            Some(Box::new(CloseAllItems { save_intent: None })),
-                            cx.handler_for(&pane, |pane, cx| {
-                                pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
-                                    .map(|task| task.detach_and_log_err(cx));
-                            }),
-                        );
-
-                    if let Some(entry) = single_entry_to_resolve {
-                        let entry_id = entry.to_proto();
-                        menu = menu.separator().entry(
-                            "Reveal In Project Panel",
-                            Some(Box::new(RevealInProjectPanel { entry_id })),
-                            cx.handler_for(&pane, move |pane, cx| {
-                                pane.project.update(cx, |_, cx| {
-                                    cx.emit(project::Event::RevealInProjectPanel(
-                                        ProjectEntryId::from_proto(entry_id),
-                                    ))
-                                });
-                            }),
-                        );
-                    }
-                }
-
-                menu
-            })
-        })
-    }
-
-    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl IntoElement {
-        TabBar::new("tab_bar")
-            .track_scroll(self.tab_bar_scroll_handle.clone())
-            .when(self.display_nav_history_buttons, |tab_bar| {
-                tab_bar.start_child(
-                    h_stack()
-                        .gap_2()
-                        .child(
-                            IconButton::new("navigate_backward", Icon::ArrowLeft)
-                                .icon_size(IconSize::Small)
-                                .on_click({
-                                    let view = cx.view().clone();
-                                    move |_, cx| view.update(cx, Self::navigate_backward)
-                                })
-                                .disabled(!self.can_navigate_backward())
-                                .tooltip(|cx| Tooltip::for_action("Go Back", &GoBack, cx)),
-                        )
-                        .child(
-                            IconButton::new("navigate_forward", Icon::ArrowRight)
-                                .icon_size(IconSize::Small)
-                                .on_click({
-                                    let view = cx.view().clone();
-                                    move |_, cx| view.update(cx, Self::navigate_backward)
-                                })
-                                .disabled(!self.can_navigate_forward())
-                                .tooltip(|cx| Tooltip::for_action("Go Forward", &GoForward, cx)),
-                        ),
-                )
-            })
-            .when(self.was_focused || self.has_focus(cx), |tab_bar| {
-                tab_bar.end_child({
-                    let render_tab_buttons = self.render_tab_bar_buttons.clone();
-                    render_tab_buttons(self, cx)
-                })
-            })
-            .children(
-                self.items
-                    .iter()
-                    .enumerate()
-                    .zip(self.tab_details(cx))
-                    .map(|((ix, item), detail)| self.render_tab(ix, item, detail, cx)),
-            )
-            .child(
-                div()
-                    .min_w_6()
-                    // HACK: This empty child is currently necessary to force the drop traget to appear
-                    // despite us setting a min width above.
-                    .child("")
-                    .h_full()
-                    .flex_grow()
-                    .drag_over::<DraggedTab>(|bar| {
-                        bar.bg(cx.theme().colors().drop_target_background)
-                    })
-                    .drag_over::<ProjectEntryId>(|bar| {
-                        bar.bg(cx.theme().colors().drop_target_background)
-                    })
-                    .on_drop(cx.listener(move |this, dragged_tab: &DraggedTab, cx| {
-                        this.drag_split_direction = None;
-                        this.handle_tab_drop(dragged_tab, this.items.len(), cx)
-                    }))
-                    .on_drop(cx.listener(move |this, entry_id: &ProjectEntryId, cx| {
-                        this.drag_split_direction = None;
-                        this.handle_project_entry_drop(entry_id, cx)
-                    })),
-            )
-    }
-
-    fn render_menu_overlay(menu: &View<ContextMenu>) -> Div {
-        div()
-            .absolute()
-            .z_index(1)
-            .bottom_0()
-            .right_0()
-            .size_0()
-            .child(overlay().anchor(AnchorCorner::TopRight).child(menu.clone()))
-    }
-
-    fn tab_details(&self, cx: &AppContext) -> Vec<usize> {
-        let mut tab_details = self.items.iter().map(|_| 0).collect::<Vec<_>>();
-
-        let mut tab_descriptions = HashMap::default();
-        let mut done = false;
-        while !done {
-            done = true;
-
-            // Store item indices by their tab description.
-            for (ix, (item, detail)) in self.items.iter().zip(&tab_details).enumerate() {
-                if let Some(description) = item.tab_description(*detail, cx) {
-                    if *detail == 0
-                        || Some(&description) != item.tab_description(detail - 1, cx).as_ref()
-                    {
-                        tab_descriptions
-                            .entry(description)
-                            .or_insert(Vec::new())
-                            .push(ix);
-                    }
-                }
-            }
-
-            // If two or more items have the same tab description, increase eir level
-            // of detail and try again.
-            for (_, item_ixs) in tab_descriptions.drain() {
-                if item_ixs.len() > 1 {
-                    done = false;
-                    for ix in item_ixs {
-                        tab_details[ix] += 1;
-                    }
-                }
-            }
-        }
-
-        tab_details
-    }
-
-    pub fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
-        self.zoomed = zoomed;
-        cx.notify();
-    }
-
-    pub fn is_zoomed(&self) -> bool {
-        self.zoomed
-    }
-
-    fn handle_drag_move<T>(&mut self, event: &DragMoveEvent<T>, cx: &mut ViewContext<Self>) {
-        if !self.can_split {
-            return;
-        }
-
-        let edge_width = cx.rem_size() * 8;
-        let cursor = event.event.position;
-        let direction = if cursor.x < event.bounds.left() + edge_width {
-            Some(SplitDirection::Left)
-        } else if cursor.x > event.bounds.right() - edge_width {
-            Some(SplitDirection::Right)
-        } else if cursor.y < event.bounds.top() + edge_width {
-            Some(SplitDirection::Up)
-        } else if cursor.y > event.bounds.bottom() - edge_width {
-            Some(SplitDirection::Down)
-        } else {
-            None
-        };
-
-        if direction != self.drag_split_direction {
-            self.drag_split_direction = direction;
-        }
-    }
-
-    fn handle_tab_drop(
-        &mut self,
-        dragged_tab: &DraggedTab,
-        ix: usize,
-        cx: &mut ViewContext<'_, Pane>,
-    ) {
-        let mut to_pane = cx.view().clone();
-        let split_direction = self.drag_split_direction;
-        let item_id = dragged_tab.item_id;
-        let from_pane = dragged_tab.pane.clone();
-        self.workspace
-            .update(cx, |_, cx| {
-                cx.defer(move |workspace, cx| {
-                    if let Some(split_direction) = split_direction {
-                        to_pane = workspace.split_pane(to_pane, split_direction, cx);
-                    }
-                    workspace.move_item(from_pane, to_pane, item_id, ix, cx);
-                });
-            })
-            .log_err();
-    }
-
-    fn handle_project_entry_drop(
-        &mut self,
-        project_entry_id: &ProjectEntryId,
-        cx: &mut ViewContext<'_, Pane>,
-    ) {
-        let mut to_pane = cx.view().clone();
-        let split_direction = self.drag_split_direction;
-        let project_entry_id = *project_entry_id;
-        self.workspace
-            .update(cx, |_, cx| {
-                cx.defer(move |workspace, cx| {
-                    if let Some(path) = workspace
-                        .project()
-                        .read(cx)
-                        .path_for_entry(project_entry_id, cx)
-                    {
-                        if let Some(split_direction) = split_direction {
-                            to_pane = workspace.split_pane(to_pane, split_direction, cx);
-                        }
-                        workspace
-                            .open_path(path, Some(to_pane.downgrade()), true, cx)
-                            .detach_and_log_err(cx);
-                    }
-                });
-            })
-            .log_err();
-    }
-
-    pub fn display_nav_history_buttons(&mut self, display: bool) {
-        self.display_nav_history_buttons = display;
-    }
-}
-
-impl FocusableView for Pane {
-    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Render for Pane {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        v_stack()
-            .key_context("Pane")
-            .track_focus(&self.focus_handle)
-            .size_full()
-            .flex_none()
-            .overflow_hidden()
-            .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, _: &SplitRight, cx| pane.split(SplitDirection::Right, 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);
-            }))
-            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateLastItem, cx| {
-                pane.activate_item(pane.items.len() - 1, true, true, cx);
-            }))
-            .on_action(cx.listener(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
-                pane.activate_prev_item(true, cx);
-            }))
-            .on_action(cx.listener(|pane: &mut Pane, _: &ActivateNextItem, cx| {
-                pane.activate_next_item(true, cx);
-            }))
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
-                    pane.close_active_item(action, cx)
-                        .map(|task| task.detach_and_log_err(cx));
-                }),
-            )
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &CloseInactiveItems, cx| {
-                    pane.close_inactive_items(action, cx)
-                        .map(|task| task.detach_and_log_err(cx));
-                }),
-            )
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &CloseCleanItems, cx| {
-                    pane.close_clean_items(action, cx)
-                        .map(|task| task.detach_and_log_err(cx));
-                }),
-            )
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &CloseItemsToTheLeft, cx| {
-                    pane.close_items_to_the_left(action, cx)
-                        .map(|task| task.detach_and_log_err(cx));
-                }),
-            )
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &CloseItemsToTheRight, cx| {
-                    pane.close_items_to_the_right(action, cx)
-                        .map(|task| task.detach_and_log_err(cx));
-                }),
-            )
-            .on_action(cx.listener(|pane: &mut Self, action: &CloseAllItems, cx| {
-                pane.close_all_items(action, cx)
-                    .map(|task| task.detach_and_log_err(cx));
-            }))
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &CloseActiveItem, cx| {
-                    pane.close_active_item(action, cx)
-                        .map(|task| task.detach_and_log_err(cx));
-                }),
-            )
-            .on_action(
-                cx.listener(|pane: &mut Self, action: &RevealInProjectPanel, cx| {
-                    pane.project.update(cx, |_, cx| {
-                        cx.emit(project::Event::RevealInProjectPanel(
-                            ProjectEntryId::from_proto(action.entry_id),
-                        ))
-                    })
-                }),
-            )
-            .when(self.active_item().is_some(), |pane| {
-                pane.child(self.render_tab_bar(cx))
-            })
-            .child({
-                let has_worktrees = self.project.read(cx).worktrees().next().is_some();
-                // main content
-                div()
-                    .flex_1()
-                    .relative()
-                    .group("")
-                    .on_drag_move::<DraggedTab>(cx.listener(Self::handle_drag_move))
-                    .on_drag_move::<ProjectEntryId>(cx.listener(Self::handle_drag_move))
-                    .map(|div| {
-                        if let Some(item) = self.active_item() {
-                            div.v_flex()
-                                .child(self.toolbar.clone())
-                                .child(item.to_any())
-                        } else {
-                            let placeholder = div.h_flex().size_full().justify_center();
-                            if has_worktrees {
-                                placeholder
-                            } else {
-                                placeholder.child(
-                                    Label::new("Open a file or project to get started.")
-                                        .color(Color::Muted),
-                                )
-                            }
-                        }
-                    })
-                    .child(
-                        // drag target
-                        div()
-                            .z_index(1)
-                            .invisible()
-                            .absolute()
-                            .bg(theme::color_alpha(
-                                cx.theme().colors().drop_target_background,
-                                0.75,
-                            ))
-                            .group_drag_over::<DraggedTab>("", |style| style.visible())
-                            .group_drag_over::<ProjectEntryId>("", |style| style.visible())
-                            .when_some(self.can_drop_predicate.clone(), |this, p| {
-                                this.can_drop(move |a, cx| p(a, cx))
-                            })
-                            .on_drop(cx.listener(move |this, dragged_tab, cx| {
-                                this.handle_tab_drop(dragged_tab, this.active_item_index(), cx)
-                            }))
-                            .on_drop(cx.listener(move |this, entry_id, cx| {
-                                this.handle_project_entry_drop(entry_id, cx)
-                            }))
-                            .map(|div| match self.drag_split_direction {
-                                None => div.top_0().left_0().right_0().bottom_0(),
-                                Some(SplitDirection::Up) => div.top_0().left_0().right_0().h_32(),
-                                Some(SplitDirection::Down) => {
-                                    div.left_0().bottom_0().right_0().h_32()
-                                }
-                                Some(SplitDirection::Left) => {
-                                    div.top_0().left_0().bottom_0().w_32()
-                                }
-                                Some(SplitDirection::Right) => {
-                                    div.top_0().bottom_0().right_0().w_32()
-                                }
-                            }),
-                    )
-            })
-            .on_mouse_down(
-                MouseButton::Navigate(NavigationDirection::Back),
-                cx.listener(|pane, _, cx| {
-                    if let Some(workspace) = pane.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)
-                            })
-                        })
-                    }
-                }),
-            )
-            .on_mouse_down(
-                MouseButton::Navigate(NavigationDirection::Forward),
-                cx.listener(|pane, _, cx| {
-                    if let Some(workspace) = pane.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)
-                            })
-                        })
-                    }
-                }),
-            )
-    }
-}
-
-impl ItemNavHistory {
-    pub fn push<D: 'static + Send + Any>(&mut self, data: Option<D>, cx: &mut WindowContext) {
-        self.history.push(data, self.item.clone(), cx);
-    }
-
-    pub fn pop_backward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        self.history.pop(NavigationMode::GoingBack, cx)
-    }
-
-    pub fn pop_forward(&mut self, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        self.history.pop(NavigationMode::GoingForward, cx)
-    }
-}
-
-impl NavHistory {
-    pub fn for_each_entry(
-        &self,
-        cx: &AppContext,
-        mut f: impl FnMut(&NavigationEntry, (ProjectPath, Option<PathBuf>)),
-    ) {
-        let borrowed_history = self.0.lock();
-        borrowed_history
-            .forward_stack
-            .iter()
-            .chain(borrowed_history.backward_stack.iter())
-            .chain(borrowed_history.closed_stack.iter())
-            .for_each(|entry| {
-                if let Some(project_and_abs_path) =
-                    borrowed_history.paths_by_item.get(&entry.item.id())
-                {
-                    f(entry, project_and_abs_path.clone());
-                } else if let Some(item) = entry.item.upgrade() {
-                    if let Some(path) = item.project_path(cx) {
-                        f(entry, (path, None));
-                    }
-                }
-            })
-    }
-
-    pub fn set_mode(&mut self, mode: NavigationMode) {
-        self.0.lock().mode = mode;
-    }
-
-    pub fn mode(&self) -> NavigationMode {
-        self.0.lock().mode
-    }
-
-    pub fn disable(&mut self) {
-        self.0.lock().mode = NavigationMode::Disabled;
-    }
-
-    pub fn enable(&mut self) {
-        self.0.lock().mode = NavigationMode::Normal;
-    }
-
-    pub fn pop(&mut self, mode: NavigationMode, cx: &mut WindowContext) -> Option<NavigationEntry> {
-        let mut state = self.0.lock();
-        let entry = match mode {
-            NavigationMode::Normal | NavigationMode::Disabled | NavigationMode::ClosingItem => {
-                return None
-            }
-            NavigationMode::GoingBack => &mut state.backward_stack,
-            NavigationMode::GoingForward => &mut state.forward_stack,
-            NavigationMode::ReopeningClosedItem => &mut state.closed_stack,
-        }
-        .pop_back();
-        if entry.is_some() {
-            state.did_update(cx);
-        }
-        entry
-    }
-
-    pub fn push<D: 'static + Send + Any>(
-        &mut self,
-        data: Option<D>,
-        item: Arc<dyn WeakItemHandle>,
-        cx: &mut WindowContext,
-    ) {
-        let state = &mut *self.0.lock();
-        match state.mode {
-            NavigationMode::Disabled => {}
-            NavigationMode::Normal | NavigationMode::ReopeningClosedItem => {
-                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.backward_stack.pop_front();
-                }
-                state.backward_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-                state.forward_stack.clear();
-            }
-            NavigationMode::GoingBack => {
-                if state.forward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.forward_stack.pop_front();
-                }
-                state.forward_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-            }
-            NavigationMode::GoingForward => {
-                if state.backward_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.backward_stack.pop_front();
-                }
-                state.backward_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-            }
-            NavigationMode::ClosingItem => {
-                if state.closed_stack.len() >= MAX_NAVIGATION_HISTORY_LEN {
-                    state.closed_stack.pop_front();
-                }
-                state.closed_stack.push_back(NavigationEntry {
-                    item,
-                    data: data.map(|data| Box::new(data) as Box<dyn Any + Send>),
-                    timestamp: state.next_timestamp.fetch_add(1, Ordering::SeqCst),
-                });
-            }
-        }
-        state.did_update(cx);
-    }
-
-    pub fn remove_item(&mut self, item_id: EntityId) {
-        let mut state = self.0.lock();
-        state.paths_by_item.remove(&item_id);
-        state
-            .backward_stack
-            .retain(|entry| entry.item.id() != item_id);
-        state
-            .forward_stack
-            .retain(|entry| entry.item.id() != item_id);
-        state
-            .closed_stack
-            .retain(|entry| entry.item.id() != item_id);
-    }
-
-    pub fn path_for_item(&self, item_id: EntityId) -> Option<(ProjectPath, Option<PathBuf>)> {
-        self.0.lock().paths_by_item.get(&item_id).cloned()
-    }
-}
-
-impl NavHistoryState {
-    pub fn did_update(&self, cx: &mut WindowContext) {
-        if let Some(pane) = self.pane.upgrade() {
-            cx.defer(move |cx| {
-                pane.update(cx, |pane, cx| pane.history_updated(cx));
-            });
-        }
-    }
-}
-
-fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
-    let path = buffer_path
-        .as_ref()
-        .and_then(|p| p.path.to_str())
-        .unwrap_or(&"This buffer");
-    let path = truncate_and_remove_front(path, 80);
-    format!("{path} contains unsaved edits. Do you want to save it?")
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::item::test::{TestItem, TestProjectItem};
-    use gpui::{TestAppContext, VisualTestContext};
-    use project::FakeFs;
-    use settings::SettingsStore;
-    use theme::LoadThemes;
-
-    #[gpui::test]
-    async fn test_remove_active_empty(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        pane.update(cx, |pane, cx| {
-            assert!(pane
-                .close_active_item(&CloseActiveItem { save_intent: None }, cx)
-                .is_none())
-        });
-    }
-
-    #[gpui::test]
-    async fn test_add_item_with_new_item(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        // 1. Add with a destination index
-        //   a. Add before the active item
-        set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| TestItem::new(cx).with_label("D"))),
-                false,
-                false,
-                Some(0),
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
-
-        //   b. Add after the active item
-        set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| TestItem::new(cx).with_label("D"))),
-                false,
-                false,
-                Some(2),
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
-
-        //   c. Add at the end of the item list (including off the length)
-        set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| TestItem::new(cx).with_label("D"))),
-                false,
-                false,
-                Some(5),
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
-        // 2. Add without a destination index
-        //   a. Add with active item at the start of the item list
-        set_labeled_items(&pane, ["A*", "B", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| TestItem::new(cx).with_label("D"))),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        set_labeled_items(&pane, ["A", "D*", "B", "C"], cx);
-
-        //   b. Add with active item at the end of the item list
-        set_labeled_items(&pane, ["A", "B", "C*"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| TestItem::new(cx).with_label("D"))),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_add_item_with_existing_item(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        // 1. Add with a destination index
-        //   1a. Add before the active item
-        let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(d, false, false, Some(0), cx);
-        });
-        assert_item_labels(&pane, ["D*", "A", "B", "C"], cx);
-
-        //   1b. Add after the active item
-        let [_, _, _, d] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(d, false, false, Some(2), cx);
-        });
-        assert_item_labels(&pane, ["A", "B", "D*", "C"], cx);
-
-        //   1c. Add at the end of the item list (including off the length)
-        let [a, _, _, _] = set_labeled_items(&pane, ["A", "B*", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(a, false, false, Some(5), cx);
-        });
-        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
-
-        //   1d. Add same item to active index
-        let [_, b, _] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(b, false, false, Some(1), cx);
-        });
-        assert_item_labels(&pane, ["A", "B*", "C"], cx);
-
-        //   1e. Add item to index after same item in last position
-        let [_, _, c] = set_labeled_items(&pane, ["A", "B*", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(c, false, false, Some(2), cx);
-        });
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
-        // 2. Add without a destination index
-        //   2a. Add with active item at the start of the item list
-        let [_, _, _, d] = set_labeled_items(&pane, ["A*", "B", "C", "D"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(d, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["A", "D*", "B", "C"], cx);
-
-        //   2b. Add with active item at the end of the item list
-        let [a, _, _, _] = set_labeled_items(&pane, ["A", "B", "C", "D*"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(a, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["B", "C", "D", "A*"], cx);
-
-        //   2c. Add active item to active item at end of list
-        let [_, _, c] = set_labeled_items(&pane, ["A", "B", "C*"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(c, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
-        //   2d. Add active item to active item at start of list
-        let [a, _, _] = set_labeled_items(&pane, ["A*", "B", "C"], cx);
-        pane.update(cx, |pane, cx| {
-            pane.add_item(a, false, false, None, cx);
-        });
-        assert_item_labels(&pane, ["A*", "B", "C"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_add_item_with_same_project_entries(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        // singleton view
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| {
-                    TestItem::new(cx)
-                        .with_singleton(true)
-                        .with_label("buffer 1")
-                        .with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-                })),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["buffer 1*"], cx);
-
-        // new singleton view with the same project entry
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| {
-                    TestItem::new(cx)
-                        .with_singleton(true)
-                        .with_label("buffer 1")
-                        .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-                })),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["buffer 1*"], cx);
-
-        // new singleton view with different project entry
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| {
-                    TestItem::new(cx)
-                        .with_singleton(true)
-                        .with_label("buffer 2")
-                        .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-                })),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["buffer 1", "buffer 2*"], cx);
-
-        // new multibuffer view with the same project entry
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| {
-                    TestItem::new(cx)
-                        .with_singleton(false)
-                        .with_label("multibuffer 1")
-                        .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-                })),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(&pane, ["buffer 1", "buffer 2", "multibuffer 1*"], cx);
-
-        // another multibuffer view with the same project entry
-        pane.update(cx, |pane, cx| {
-            pane.add_item(
-                Box::new(cx.new_view(|cx| {
-                    TestItem::new(cx)
-                        .with_singleton(false)
-                        .with_label("multibuffer 1b")
-                        .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-                })),
-                false,
-                false,
-                None,
-                cx,
-            );
-        });
-        assert_item_labels(
-            &pane,
-            ["buffer 1", "buffer 2", "multibuffer 1", "multibuffer 1b*"],
-            cx,
-        );
-    }
-
-    #[gpui::test]
-    async fn test_remove_item_ordering(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        add_labeled_item(&pane, "A", false, cx);
-        add_labeled_item(&pane, "B", false, cx);
-        add_labeled_item(&pane, "C", false, cx);
-        add_labeled_item(&pane, "D", false, cx);
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
-        pane.update(cx, |pane, cx| pane.activate_item(1, false, false, cx));
-        add_labeled_item(&pane, "1", false, cx);
-        assert_item_labels(&pane, ["A", "B", "1*", "C", "D"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "B*", "C", "D"], cx);
-
-        pane.update(cx, |pane, cx| pane.activate_item(3, false, false, cx));
-        assert_item_labels(&pane, ["A", "B", "C", "D*"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "B*", "C"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "C*"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_active_item(&CloseActiveItem { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_inactive_items(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_inactive_items(&CloseInactiveItems, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["C*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_clean_items(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        add_labeled_item(&pane, "A", true, cx);
-        add_labeled_item(&pane, "B", false, cx);
-        add_labeled_item(&pane, "C", true, cx);
-        add_labeled_item(&pane, "D", false, cx);
-        add_labeled_item(&pane, "E", false, cx);
-        assert_item_labels(&pane, ["A^", "B", "C^", "D", "E*"], cx);
-
-        pane.update(cx, |pane, cx| pane.close_clean_items(&CloseCleanItems, cx))
-            .unwrap()
-            .await
-            .unwrap();
-        assert_item_labels(&pane, ["A^", "C*^"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_items_to_the_left(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_items_to_the_left(&CloseItemsToTheLeft, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["C*", "D", "E"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_items_to_the_right(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_items_to_the_right(&CloseItemsToTheRight, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-    }
-
-    #[gpui::test]
-    async fn test_close_all_items(cx: &mut TestAppContext) {
-        init_test(cx);
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, 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());
-
-        add_labeled_item(&pane, "A", false, cx);
-        add_labeled_item(&pane, "B", false, cx);
-        add_labeled_item(&pane, "C", false, cx);
-        assert_item_labels(&pane, ["A", "B", "C*"], cx);
-
-        pane.update(cx, |pane, cx| {
-            pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
-        })
-        .unwrap()
-        .await
-        .unwrap();
-        assert_item_labels(&pane, [], cx);
-
-        add_labeled_item(&pane, "A", true, cx);
-        add_labeled_item(&pane, "B", true, cx);
-        add_labeled_item(&pane, "C", true, cx);
-        assert_item_labels(&pane, ["A^", "B^", "C*^"], cx);
-
-        let save = pane
-            .update(cx, |pane, cx| {
-                pane.close_all_items(&CloseAllItems { save_intent: None }, cx)
-            })
-            .unwrap();
-
-        cx.executor().run_until_parked();
-        cx.simulate_prompt_answer(2);
-        save.await.unwrap();
-        assert_item_labels(&pane, [], cx);
-    }
-
-    fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let settings_store = SettingsStore::test(cx);
-            cx.set_global(settings_store);
-            theme::init(LoadThemes::JustBase, cx);
-            crate::init_settings(cx);
-            Project::init_settings(cx);
-        });
-    }
-
-    fn add_labeled_item(
-        pane: &View<Pane>,
-        label: &str,
-        is_dirty: bool,
-        cx: &mut VisualTestContext,
-    ) -> Box<View<TestItem>> {
-        pane.update(cx, |pane, cx| {
-            let labeled_item = Box::new(
-                cx.new_view(|cx| TestItem::new(cx).with_label(label).with_dirty(is_dirty)),
-            );
-            pane.add_item(labeled_item.clone(), false, false, None, cx);
-            labeled_item
-        })
-    }
-
-    fn set_labeled_items<const COUNT: usize>(
-        pane: &View<Pane>,
-        labels: [&str; COUNT],
-        cx: &mut VisualTestContext,
-    ) -> [Box<View<TestItem>>; COUNT] {
-        pane.update(cx, |pane, cx| {
-            pane.items.clear();
-            let mut active_item_index = 0;
-
-            let mut index = 0;
-            let items = labels.map(|mut label| {
-                if label.ends_with("*") {
-                    label = label.trim_end_matches("*");
-                    active_item_index = index;
-                }
-
-                let labeled_item = Box::new(cx.new_view(|cx| TestItem::new(cx).with_label(label)));
-                pane.add_item(labeled_item.clone(), false, false, None, cx);
-                index += 1;
-                labeled_item
-            });
-
-            pane.activate_item(active_item_index, false, false, cx);
-
-            items
-        })
-    }
-
-    // Assert the item label, with the active item label suffixed with a '*'
-    fn assert_item_labels<const COUNT: usize>(
-        pane: &View<Pane>,
-        expected_states: [&str; COUNT],
-        cx: &mut VisualTestContext,
-    ) {
-        pane.update(cx, |pane, cx| {
-            let actual_states = pane
-                .items
-                .iter()
-                .enumerate()
-                .map(|(ix, item)| {
-                    let mut state = item
-                        .to_any()
-                        .downcast::<TestItem>()
-                        .unwrap()
-                        .read(cx)
-                        .label
-                        .clone();
-                    if ix == pane.active_item_index {
-                        state.push('*');
-                    }
-                    if item.is_dirty(cx) {
-                        state.push('^');
-                    }
-                    state
-                })
-                .collect::<Vec<_>>();
-
-            assert_eq!(
-                actual_states, expected_states,
-                "pane items do not match expectation"
-            );
-        })
-    }
-}
-
-impl Render for DraggedTab {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let ui_font = ThemeSettings::get_global(cx).ui_font.family.clone();
-        let item = &self.pane.read(cx).items[self.ix];
-        let label = item.tab_content(Some(self.detail), false, cx);
-        Tab::new("")
-            .selected(self.is_active)
-            .child(label)
-            .render(cx)
-            .font(ui_font)
-    }
-}

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

@@ -1,239 +0,0 @@
-use super::DraggedItem;
-use crate::{Pane, SplitDirection, Workspace};
-use gpui::{
-    color::Color,
-    elements::{Canvas, MouseEventHandler, ParentComponent, Stack},
-    geometry::{rect::RectF, vector::Vector2F},
-    platform::MouseButton,
-    scene::MouseUp,
-    AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
-};
-use project2::ProjectEntryId;
-
-pub fn dragged_item_receiver<Tag, D, F>(
-    pane: &Pane,
-    region_id: usize,
-    drop_index: usize,
-    allow_same_pane: bool,
-    split_margin: Option<f32>,
-    cx: &mut ViewContext<Pane>,
-    render_child: F,
-) -> MouseEventHandler<Pane>
-where
-    Tag: 'static,
-    D: Element<Pane>,
-    F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
-{
-    let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
-    let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
-        drag_and_drop
-            .currently_dragged::<DraggedItem>(cx.window())
-            .map(|(drag_position, _)| drag_position)
-            .or_else(|| {
-                drag_and_drop
-                    .currently_dragged::<ProjectEntryId>(cx.window())
-                    .map(|(drag_position, _)| drag_position)
-            })
-    } else {
-        None
-    };
-
-    let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
-        // Observing hovered will cause a render when the mouse enters regardless
-        // of if mouse position was accessed before
-        let drag_position = if state.dragging() {
-            drag_position
-        } else {
-            None
-        };
-        Stack::new()
-            .with_child(render_child(state, cx))
-            .with_children(drag_position.map(|drag_position| {
-                Canvas::new(move |bounds, _, _, cx| {
-                    if bounds.contains_point(drag_position) {
-                        let overlay_region = split_margin
-                            .and_then(|split_margin| {
-                                drop_split_direction(drag_position, bounds, split_margin)
-                                    .map(|dir| (dir, split_margin))
-                            })
-                            .map(|(dir, margin)| dir.along_edge(bounds, margin))
-                            .unwrap_or(bounds);
-
-                        cx.scene().push_stacking_context(None, None);
-                        let background = overlay_color(cx);
-                        cx.scene().push_quad(Quad {
-                            bounds: overlay_region,
-                            background: Some(background),
-                            border: Default::default(),
-                            corner_radii: Default::default(),
-                        });
-                        cx.scene().pop_stacking_context();
-                    }
-                })
-            }))
-    });
-
-    if drag_position.is_some() {
-        handler = handler
-            .on_up(MouseButton::Left, {
-                move |event, pane, cx| {
-                    let workspace = pane.workspace.clone();
-                    let pane = cx.weak_handle();
-                    handle_dropped_item(
-                        event,
-                        workspace,
-                        &pane,
-                        drop_index,
-                        allow_same_pane,
-                        split_margin,
-                        cx,
-                    );
-                    cx.notify();
-                }
-            })
-            .on_move(|_, _, cx| {
-                let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
-
-                if drag_and_drop
-                    .currently_dragged::<DraggedItem>(cx.window())
-                    .is_some()
-                    || drag_and_drop
-                        .currently_dragged::<ProjectEntryId>(cx.window())
-                        .is_some()
-                {
-                    cx.notify();
-                } else {
-                    cx.propagate_event();
-                }
-            })
-    }
-
-    handler
-}
-
-pub fn handle_dropped_item<V: 'static>(
-    event: MouseUp,
-    workspace: WeakViewHandle<Workspace>,
-    pane: &WeakViewHandle<Pane>,
-    index: usize,
-    allow_same_pane: bool,
-    split_margin: Option<f32>,
-    cx: &mut EventContext<V>,
-) {
-    enum Action {
-        Move(WeakViewHandle<Pane>, usize),
-        Open(ProjectEntryId),
-    }
-    let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
-    let action = if let Some((_, dragged_item)) =
-        drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
-    {
-        Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
-    } else if let Some((_, project_entry)) =
-        drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
-    {
-        Action::Open(*project_entry)
-    } else {
-        cx.propagate_event();
-        return;
-    };
-
-    if let Some(split_direction) =
-        split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
-    {
-        let pane_to_split = pane.clone();
-        match action {
-            Action::Move(from, item_id_to_move) => {
-                cx.window_context().defer(move |cx| {
-                    if let Some(workspace) = workspace.upgrade(cx) {
-                        workspace.update(cx, |workspace, cx| {
-                            workspace.split_pane_with_item(
-                                pane_to_split,
-                                split_direction,
-                                from,
-                                item_id_to_move,
-                                cx,
-                            );
-                        })
-                    }
-                });
-            }
-            Action::Open(project_entry) => {
-                cx.window_context().defer(move |cx| {
-                    if let Some(workspace) = workspace.upgrade(cx) {
-                        workspace.update(cx, |workspace, cx| {
-                            if let Some(task) = workspace.split_pane_with_project_entry(
-                                pane_to_split,
-                                split_direction,
-                                project_entry,
-                                cx,
-                            ) {
-                                task.detach_and_log_err(cx);
-                            }
-                        })
-                    }
-                });
-            }
-        };
-    } else {
-        match action {
-            Action::Move(from, item_id) => {
-                if pane != &from || allow_same_pane {
-                    let pane = pane.clone();
-                    cx.window_context().defer(move |cx| {
-                        if let Some(((workspace, from), to)) = workspace
-                            .upgrade(cx)
-                            .zip(from.upgrade(cx))
-                            .zip(pane.upgrade(cx))
-                        {
-                            workspace.update(cx, |workspace, cx| {
-                                workspace.move_item(from, to, item_id, index, cx);
-                            })
-                        }
-                    });
-                } else {
-                    cx.propagate_event();
-                }
-            }
-            Action::Open(project_entry) => {
-                let pane = pane.clone();
-                cx.window_context().defer(move |cx| {
-                    if let Some(workspace) = workspace.upgrade(cx) {
-                        workspace.update(cx, |workspace, cx| {
-                            if let Some(path) =
-                                workspace.project.read(cx).path_for_entry(project_entry, cx)
-                            {
-                                workspace
-                                    .open_path(path, Some(pane), true, cx)
-                                    .detach_and_log_err(cx);
-                            }
-                        });
-                    }
-                });
-            }
-        }
-    }
-}
-
-fn drop_split_direction(
-    position: Vector2F,
-    region: RectF,
-    split_margin: f32,
-) -> Option<SplitDirection> {
-    let mut min_direction = None;
-    let mut min_distance = split_margin;
-    for direction in SplitDirection::all() {
-        let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
-
-        if edge_distance < min_distance {
-            min_direction = Some(direction);
-            min_distance = edge_distance;
-        }
-    }
-
-    min_direction
-}
-
-fn overlay_color(cx: &AppContext) -> Color {
-    theme2::current(cx).workspace.drop_target_overlay_color
-}

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

@@ -1,865 +0,0 @@
-use crate::{pane_group::element::pane_axis, AppState, FollowerState, Pane, Workspace};
-use anyhow::{anyhow, Result};
-use call::{ActiveCall, ParticipantLocation};
-use collections::HashMap;
-use gpui::{
-    point, size, AnyWeakView, Axis, Bounds, Entity as _, IntoElement, Model, Pixels, Point, View,
-    ViewContext,
-};
-use parking_lot::Mutex;
-use project::Project;
-use serde::Deserialize;
-use std::sync::Arc;
-use ui::{prelude::*, Button};
-
-const HANDLE_HITBOX_SIZE: f32 = 10.0; //todo!(change this back to 4)
-const HORIZONTAL_MIN_SIZE: f32 = 80.;
-const VERTICAL_MIN_SIZE: f32 = 100.;
-
-#[derive(Clone)]
-pub struct PaneGroup {
-    pub(crate) root: Member,
-}
-
-impl PaneGroup {
-    pub(crate) fn with_root(root: Member) -> Self {
-        Self { root }
-    }
-
-    pub fn new(pane: View<Pane>) -> Self {
-        Self {
-            root: Member::Pane(pane),
-        }
-    }
-
-    pub fn split(
-        &mut self,
-        old_pane: &View<Pane>,
-        new_pane: &View<Pane>,
-        direction: SplitDirection,
-    ) -> Result<()> {
-        match &mut self.root {
-            Member::Pane(pane) => {
-                if pane == old_pane {
-                    self.root = Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
-                    Ok(())
-                } else {
-                    Err(anyhow!("Pane not found"))
-                }
-            }
-            Member::Axis(axis) => axis.split(old_pane, new_pane, direction),
-        }
-    }
-
-    pub fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
-        match &self.root {
-            Member::Pane(_) => None,
-            Member::Axis(axis) => axis.bounding_box_for_pane(pane),
-        }
-    }
-
-    pub fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
-        match &self.root {
-            Member::Pane(pane) => Some(pane),
-            Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
-        }
-    }
-
-    /// Returns:
-    /// - Ok(true) if it found and removed a pane
-    /// - Ok(false) if it found but did not remove the pane
-    /// - Err(_) if it did not find the pane
-    pub fn remove(&mut self, pane: &View<Pane>) -> Result<bool> {
-        match &mut self.root {
-            Member::Pane(_) => Ok(false),
-            Member::Axis(axis) => {
-                if let Some(last_pane) = axis.remove(pane)? {
-                    self.root = last_pane;
-                }
-                Ok(true)
-            }
-        }
-    }
-
-    pub fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
-        match &mut self.root {
-            Member::Pane(_) => {}
-            Member::Axis(axis) => axis.swap(from, to),
-        };
-    }
-
-    pub(crate) fn render(
-        &self,
-        project: &Model<Project>,
-        follower_states: &HashMap<View<Pane>, FollowerState>,
-        active_call: Option<&Model<ActiveCall>>,
-        active_pane: &View<Pane>,
-        zoomed: Option<&AnyWeakView>,
-        app_state: &Arc<AppState>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> impl IntoElement {
-        self.root.render(
-            project,
-            0,
-            follower_states,
-            active_call,
-            active_pane,
-            zoomed,
-            app_state,
-            cx,
-        )
-    }
-
-    pub(crate) fn panes(&self) -> Vec<&View<Pane>> {
-        let mut panes = Vec::new();
-        self.root.collect_panes(&mut panes);
-        panes
-    }
-
-    pub(crate) fn first_pane(&self) -> View<Pane> {
-        self.root.first_pane()
-    }
-}
-
-#[derive(Clone)]
-pub(crate) enum Member {
-    Axis(PaneAxis),
-    Pane(View<Pane>),
-}
-
-impl Member {
-    fn new_axis(old_pane: View<Pane>, new_pane: View<Pane>, direction: SplitDirection) -> Self {
-        use Axis::*;
-        use SplitDirection::*;
-
-        let axis = match direction {
-            Up | Down => Vertical,
-            Left | Right => Horizontal,
-        };
-
-        let members = match direction {
-            Up | Left => vec![Member::Pane(new_pane), Member::Pane(old_pane)],
-            Down | Right => vec![Member::Pane(old_pane), Member::Pane(new_pane)],
-        };
-
-        Member::Axis(PaneAxis::new(axis, members))
-    }
-
-    fn contains(&self, needle: &View<Pane>) -> bool {
-        match self {
-            Member::Axis(axis) => axis.members.iter().any(|member| member.contains(needle)),
-            Member::Pane(pane) => pane == needle,
-        }
-    }
-
-    fn first_pane(&self) -> View<Pane> {
-        match self {
-            Member::Axis(axis) => axis.members[0].first_pane(),
-            Member::Pane(pane) => pane.clone(),
-        }
-    }
-
-    pub fn render(
-        &self,
-        project: &Model<Project>,
-        basis: usize,
-        follower_states: &HashMap<View<Pane>, FollowerState>,
-        active_call: Option<&Model<ActiveCall>>,
-        active_pane: &View<Pane>,
-        zoomed: Option<&AnyWeakView>,
-        app_state: &Arc<AppState>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> impl IntoElement {
-        match self {
-            Member::Pane(pane) => {
-                if zoomed == Some(&pane.downgrade().into()) {
-                    return div().into_any();
-                }
-
-                let leader = follower_states.get(pane).and_then(|state| {
-                    let room = active_call?.read(cx).room()?.read(cx);
-                    room.remote_participant_for_peer_id(state.leader_id)
-                });
-
-                let mut leader_border = None;
-                let mut leader_status_box = None;
-                if let Some(leader) = &leader {
-                    let mut leader_color = cx
-                        .theme()
-                        .players()
-                        .color_for_participant(leader.participant_index.0)
-                        .cursor;
-                    leader_color.fade_out(0.3);
-                    leader_border = Some(leader_color);
-
-                    leader_status_box = match leader.location {
-                        ParticipantLocation::SharedProject {
-                            project_id: leader_project_id,
-                        } => {
-                            if Some(leader_project_id) == project.read(cx).remote_id() {
-                                None
-                            } else {
-                                let leader_user = leader.user.clone();
-                                let leader_user_id = leader.user.id;
-                                Some(
-                                    Button::new(
-                                        ("leader-status", pane.entity_id()),
-                                        format!(
-                                            "Follow {} to their active project",
-                                            leader_user.github_login,
-                                        ),
-                                    )
-                                    .on_click(cx.listener(
-                                        move |this, _, cx| {
-                                            crate::join_remote_project(
-                                                leader_project_id,
-                                                leader_user_id,
-                                                this.app_state().clone(),
-                                                cx,
-                                            )
-                                            .detach_and_log_err(cx);
-                                        },
-                                    )),
-                                )
-                            }
-                        }
-                        ParticipantLocation::UnsharedProject => Some(Button::new(
-                            ("leader-status", pane.entity_id()),
-                            format!(
-                                "{} is viewing an unshared Zed project",
-                                leader.user.github_login
-                            ),
-                        )),
-                        ParticipantLocation::External => Some(Button::new(
-                            ("leader-status", pane.entity_id()),
-                            format!(
-                                "{} is viewing a window outside of Zed",
-                                leader.user.github_login
-                            ),
-                        )),
-                    };
-                }
-
-                div()
-                    .relative()
-                    .flex_1()
-                    .size_full()
-                    .child(pane.clone())
-                    .when_some(leader_border, |this, color| {
-                        this.border_2().border_color(color)
-                    })
-                    .when_some(leader_status_box, |this, status_box| {
-                        this.child(
-                            div()
-                                .absolute()
-                                .w_96()
-                                .bottom_3()
-                                .right_3()
-                                .z_index(1)
-                                .child(status_box),
-                        )
-                    })
-                    .into_any()
-
-                // let el = div()
-                //     .flex()
-                //     .flex_1()
-                //     .gap_px()
-                //     .w_full()
-                //     .h_full()
-                //     .bg(cx.theme().colors().editor)
-                //     .children();
-            }
-            Member::Axis(axis) => axis
-                .render(
-                    project,
-                    basis + 1,
-                    follower_states,
-                    active_call,
-                    active_pane,
-                    zoomed,
-                    app_state,
-                    cx,
-                )
-                .into_any(),
-        }
-    }
-
-    fn collect_panes<'a>(&'a self, panes: &mut Vec<&'a View<Pane>>) {
-        match self {
-            Member::Axis(axis) => {
-                for member in &axis.members {
-                    member.collect_panes(panes);
-                }
-            }
-            Member::Pane(pane) => panes.push(pane),
-        }
-    }
-}
-
-#[derive(Clone)]
-pub(crate) struct PaneAxis {
-    pub axis: Axis,
-    pub members: Vec<Member>,
-    pub flexes: Arc<Mutex<Vec<f32>>>,
-    pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
-}
-
-impl PaneAxis {
-    pub fn new(axis: Axis, members: Vec<Member>) -> Self {
-        let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
-        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
-        Self {
-            axis,
-            members,
-            flexes,
-            bounding_boxes,
-        }
-    }
-
-    pub fn load(axis: Axis, members: Vec<Member>, flexes: Option<Vec<f32>>) -> Self {
-        let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
-        debug_assert!(members.len() == flexes.len());
-
-        let flexes = Arc::new(Mutex::new(flexes));
-        let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
-        Self {
-            axis,
-            members,
-            flexes,
-            bounding_boxes,
-        }
-    }
-
-    fn split(
-        &mut self,
-        old_pane: &View<Pane>,
-        new_pane: &View<Pane>,
-        direction: SplitDirection,
-    ) -> Result<()> {
-        for (mut idx, member) in self.members.iter_mut().enumerate() {
-            match member {
-                Member::Axis(axis) => {
-                    if axis.split(old_pane, new_pane, direction).is_ok() {
-                        return Ok(());
-                    }
-                }
-                Member::Pane(pane) => {
-                    if pane == old_pane {
-                        if direction.axis() == self.axis {
-                            if direction.increasing() {
-                                idx += 1;
-                            }
-
-                            self.members.insert(idx, Member::Pane(new_pane.clone()));
-                            *self.flexes.lock() = vec![1.; self.members.len()];
-                        } else {
-                            *member =
-                                Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
-                        }
-                        return Ok(());
-                    }
-                }
-            }
-        }
-        Err(anyhow!("Pane not found"))
-    }
-
-    fn remove(&mut self, pane_to_remove: &View<Pane>) -> Result<Option<Member>> {
-        let mut found_pane = false;
-        let mut remove_member = None;
-        for (idx, member) in self.members.iter_mut().enumerate() {
-            match member {
-                Member::Axis(axis) => {
-                    if let Ok(last_pane) = axis.remove(pane_to_remove) {
-                        if let Some(last_pane) = last_pane {
-                            *member = last_pane;
-                        }
-                        found_pane = true;
-                        break;
-                    }
-                }
-                Member::Pane(pane) => {
-                    if pane == pane_to_remove {
-                        found_pane = true;
-                        remove_member = Some(idx);
-                        break;
-                    }
-                }
-            }
-        }
-
-        if found_pane {
-            if let Some(idx) = remove_member {
-                self.members.remove(idx);
-                *self.flexes.lock() = vec![1.; self.members.len()];
-            }
-
-            if self.members.len() == 1 {
-                let result = self.members.pop();
-                *self.flexes.lock() = vec![1.; self.members.len()];
-                Ok(result)
-            } else {
-                Ok(None)
-            }
-        } else {
-            Err(anyhow!("Pane not found"))
-        }
-    }
-
-    fn swap(&mut self, from: &View<Pane>, to: &View<Pane>) {
-        for member in self.members.iter_mut() {
-            match member {
-                Member::Axis(axis) => axis.swap(from, to),
-                Member::Pane(pane) => {
-                    if pane == from {
-                        *member = Member::Pane(to.clone());
-                    } else if pane == to {
-                        *member = Member::Pane(from.clone())
-                    }
-                }
-            }
-        }
-    }
-
-    fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
-        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
-
-        for (idx, member) in self.members.iter().enumerate() {
-            match member {
-                Member::Pane(found) => {
-                    if pane == found {
-                        return self.bounding_boxes.lock()[idx];
-                    }
-                }
-                Member::Axis(axis) => {
-                    if let Some(rect) = axis.bounding_box_for_pane(pane) {
-                        return Some(rect);
-                    }
-                }
-            }
-        }
-        None
-    }
-
-    fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
-        debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
-
-        let bounding_boxes = self.bounding_boxes.lock();
-
-        for (idx, member) in self.members.iter().enumerate() {
-            if let Some(coordinates) = bounding_boxes[idx] {
-                if coordinates.contains(&coordinate) {
-                    return match member {
-                        Member::Pane(found) => Some(found),
-                        Member::Axis(axis) => axis.pane_at_pixel_position(coordinate),
-                    };
-                }
-            }
-        }
-        None
-    }
-
-    fn render(
-        &self,
-        project: &Model<Project>,
-        basis: usize,
-        follower_states: &HashMap<View<Pane>, FollowerState>,
-        active_call: Option<&Model<ActiveCall>>,
-        active_pane: &View<Pane>,
-        zoomed: Option<&AnyWeakView>,
-        app_state: &Arc<AppState>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> gpui::AnyElement {
-        debug_assert!(self.members.len() == self.flexes.lock().len());
-        let mut active_pane_ix = None;
-
-        pane_axis(
-            self.axis,
-            basis,
-            self.flexes.clone(),
-            self.bounding_boxes.clone(),
-        )
-        .children(self.members.iter().enumerate().map(|(ix, member)| {
-            if member.contains(active_pane) {
-                active_pane_ix = Some(ix);
-            }
-            member
-                .render(
-                    project,
-                    (basis + ix) * 10,
-                    follower_states,
-                    active_call,
-                    active_pane,
-                    zoomed,
-                    app_state,
-                    cx,
-                )
-                .into_any_element()
-        }))
-        .with_active_pane(active_pane_ix)
-        .into_any_element()
-    }
-}
-
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
-pub enum SplitDirection {
-    Up,
-    Down,
-    Left,
-    Right,
-}
-
-impl SplitDirection {
-    pub fn all() -> [Self; 4] {
-        [Self::Up, Self::Down, Self::Left, Self::Right]
-    }
-
-    pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
-        match self {
-            Self::Up => rect.origin.y,
-            Self::Down => rect.lower_left().y,
-            Self::Left => rect.lower_left().x,
-            Self::Right => rect.lower_right().x,
-        }
-    }
-
-    pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
-        match self {
-            Self::Up => Bounds {
-                origin: bounds.origin,
-                size: size(bounds.size.width, length),
-            },
-            Self::Down => Bounds {
-                origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
-                size: size(bounds.size.width, length),
-            },
-            Self::Left => Bounds {
-                origin: bounds.origin,
-                size: size(length, bounds.size.height),
-            },
-            Self::Right => Bounds {
-                origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
-                size: size(length, bounds.size.height),
-            },
-        }
-    }
-
-    pub fn axis(&self) -> Axis {
-        match self {
-            Self::Up | Self::Down => Axis::Vertical,
-            Self::Left | Self::Right => Axis::Horizontal,
-        }
-    }
-
-    pub fn increasing(&self) -> bool {
-        match self {
-            Self::Left | Self::Up => false,
-            Self::Down | Self::Right => true,
-        }
-    }
-}
-
-mod element {
-
-    use std::{cell::RefCell, iter, rc::Rc, sync::Arc};
-
-    use gpui::{
-        px, relative, Along, AnyElement, Axis, Bounds, CursorStyle, Element, InteractiveBounds,
-        IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point,
-        Size, Style, WindowContext,
-    };
-    use parking_lot::Mutex;
-    use smallvec::SmallVec;
-    use ui::prelude::*;
-
-    use super::{HANDLE_HITBOX_SIZE, HORIZONTAL_MIN_SIZE, VERTICAL_MIN_SIZE};
-
-    const DIVIDER_SIZE: f32 = 1.0;
-
-    pub fn pane_axis(
-        axis: Axis,
-        basis: usize,
-        flexes: Arc<Mutex<Vec<f32>>>,
-        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
-    ) -> PaneAxisElement {
-        PaneAxisElement {
-            axis,
-            basis,
-            flexes,
-            bounding_boxes,
-            children: SmallVec::new(),
-            active_pane_ix: None,
-        }
-    }
-
-    pub struct PaneAxisElement {
-        axis: Axis,
-        basis: usize,
-        flexes: Arc<Mutex<Vec<f32>>>,
-        bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
-        children: SmallVec<[AnyElement; 2]>,
-        active_pane_ix: Option<usize>,
-    }
-
-    impl PaneAxisElement {
-        pub fn with_active_pane(mut self, active_pane_ix: Option<usize>) -> Self {
-            self.active_pane_ix = active_pane_ix;
-            self
-        }
-
-        fn compute_resize(
-            flexes: &Arc<Mutex<Vec<f32>>>,
-            e: &MouseMoveEvent,
-            ix: usize,
-            axis: Axis,
-            child_start: Point<Pixels>,
-            container_size: Size<Pixels>,
-            cx: &mut WindowContext,
-        ) {
-            let min_size = match axis {
-                Axis::Horizontal => px(HORIZONTAL_MIN_SIZE),
-                Axis::Vertical => px(VERTICAL_MIN_SIZE),
-            };
-            let mut flexes = flexes.lock();
-            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
-
-            let size = move |ix, flexes: &[f32]| {
-                container_size.along(axis) * (flexes[ix] / flexes.len() as f32)
-            };
-
-            // Don't allow resizing to less than the minimum size, if elements are already too small
-            if min_size - px(1.) > size(ix, flexes.as_slice()) {
-                return;
-            }
-
-            let mut proposed_current_pixel_change =
-                (e.position - child_start).along(axis) - size(ix, flexes.as_slice());
-
-            let flex_changes = |pixel_dx, target_ix, next: isize, flexes: &[f32]| {
-                let flex_change = pixel_dx / container_size.along(axis);
-                let current_target_flex = flexes[target_ix] + flex_change;
-                let next_target_flex = flexes[(target_ix as isize + next) as usize] - flex_change;
-                (current_target_flex, next_target_flex)
-            };
-
-            let mut successors = iter::from_fn({
-                let forward = proposed_current_pixel_change > px(0.);
-                let mut ix_offset = 0;
-                let len = flexes.len();
-                move || {
-                    let result = if forward {
-                        (ix + 1 + ix_offset < len).then(|| ix + ix_offset)
-                    } else {
-                        (ix as isize - ix_offset as isize >= 0).then(|| ix - ix_offset)
-                    };
-
-                    ix_offset += 1;
-
-                    result
-                }
-            });
-
-            while proposed_current_pixel_change.abs() > px(0.) {
-                let Some(current_ix) = successors.next() else {
-                    break;
-                };
-
-                let next_target_size = Pixels::max(
-                    size(current_ix + 1, flexes.as_slice()) - proposed_current_pixel_change,
-                    min_size,
-                );
-
-                let current_target_size = Pixels::max(
-                    size(current_ix, flexes.as_slice()) + size(current_ix + 1, flexes.as_slice())
-                        - next_target_size,
-                    min_size,
-                );
-
-                let current_pixel_change =
-                    current_target_size - size(current_ix, flexes.as_slice());
-
-                let (current_target_flex, next_target_flex) =
-                    flex_changes(current_pixel_change, current_ix, 1, flexes.as_slice());
-
-                flexes[current_ix] = current_target_flex;
-                flexes[current_ix + 1] = next_target_flex;
-
-                proposed_current_pixel_change -= current_pixel_change;
-            }
-
-            // todo!(schedule serialize)
-            // workspace.schedule_serialize(cx);
-            cx.notify();
-        }
-
-        fn push_handle(
-            flexes: Arc<Mutex<Vec<f32>>>,
-            dragged_handle: Rc<RefCell<Option<usize>>>,
-            axis: Axis,
-            ix: usize,
-            pane_bounds: Bounds<Pixels>,
-            axis_bounds: Bounds<Pixels>,
-            cx: &mut WindowContext,
-        ) {
-            let handle_bounds = Bounds {
-                origin: pane_bounds.origin.apply_along(axis, |origin| {
-                    origin + pane_bounds.size.along(axis) - px(HANDLE_HITBOX_SIZE / 2.)
-                }),
-                size: pane_bounds
-                    .size
-                    .apply_along(axis, |_| px(HANDLE_HITBOX_SIZE)),
-            };
-            let divider_bounds = Bounds {
-                origin: pane_bounds
-                    .origin
-                    .apply_along(axis, |origin| origin + pane_bounds.size.along(axis)),
-                size: pane_bounds.size.apply_along(axis, |_| px(DIVIDER_SIZE)),
-            };
-
-            cx.with_z_index(3, |cx| {
-                let interactive_handle_bounds = InteractiveBounds {
-                    bounds: handle_bounds,
-                    stacking_order: cx.stacking_order().clone(),
-                };
-                if interactive_handle_bounds.visibly_contains(&cx.mouse_position(), cx) {
-                    cx.set_cursor_style(match axis {
-                        Axis::Vertical => CursorStyle::ResizeUpDown,
-                        Axis::Horizontal => CursorStyle::ResizeLeftRight,
-                    })
-                }
-
-                cx.add_opaque_layer(handle_bounds);
-                cx.paint_quad(gpui::fill(divider_bounds, cx.theme().colors().border));
-
-                cx.on_mouse_event({
-                    let dragged_handle = dragged_handle.clone();
-                    move |e: &MouseDownEvent, phase, _cx| {
-                        if phase.bubble() && handle_bounds.contains(&e.position) {
-                            dragged_handle.replace(Some(ix));
-                        }
-                    }
-                });
-                cx.on_mouse_event(move |e: &MouseMoveEvent, phase, cx| {
-                    let dragged_handle = dragged_handle.borrow();
-                    if phase.bubble() && *dragged_handle == Some(ix) {
-                        Self::compute_resize(
-                            &flexes,
-                            e,
-                            ix,
-                            axis,
-                            pane_bounds.origin,
-                            axis_bounds.size,
-                            cx,
-                        )
-                    }
-                });
-            });
-        }
-    }
-
-    impl IntoElement for PaneAxisElement {
-        type Element = Self;
-
-        fn element_id(&self) -> Option<ui::prelude::ElementId> {
-            Some(self.basis.into())
-        }
-
-        fn into_element(self) -> Self::Element {
-            self
-        }
-    }
-
-    impl Element for PaneAxisElement {
-        type State = Rc<RefCell<Option<usize>>>;
-
-        fn request_layout(
-            &mut self,
-            state: Option<Self::State>,
-            cx: &mut ui::prelude::WindowContext,
-        ) -> (gpui::LayoutId, Self::State) {
-            let mut style = Style::default();
-            style.flex_grow = 1.;
-            style.flex_shrink = 1.;
-            style.flex_basis = relative(0.).into();
-            style.size.width = relative(1.).into();
-            style.size.height = relative(1.).into();
-            let layout_id = cx.request_layout(&style, None);
-            let dragged_pane = state.unwrap_or_else(|| Rc::new(RefCell::new(None)));
-            (layout_id, dragged_pane)
-        }
-
-        fn paint(
-            &mut self,
-            bounds: gpui::Bounds<ui::prelude::Pixels>,
-            state: &mut Self::State,
-            cx: &mut ui::prelude::WindowContext,
-        ) {
-            let flexes = self.flexes.lock().clone();
-            let len = self.children.len();
-            debug_assert!(flexes.len() == len);
-            debug_assert!(flex_values_in_bounds(flexes.as_slice()));
-
-            let mut origin = bounds.origin;
-            let space_per_flex = bounds.size.along(self.axis) / len as f32;
-
-            let mut bounding_boxes = self.bounding_boxes.lock();
-            bounding_boxes.clear();
-
-            for (ix, child) in self.children.iter_mut().enumerate() {
-                //todo!(active_pane_magnification)
-                // If using active pane magnification, need to switch to using
-                // 1 for all non-active panes, and then the magnification for the
-                // active pane.
-                let child_size = bounds
-                    .size
-                    .apply_along(self.axis, |_| space_per_flex * flexes[ix]);
-
-                let child_bounds = Bounds {
-                    origin,
-                    size: child_size,
-                };
-                bounding_boxes.push(Some(child_bounds));
-                cx.with_z_index(0, |cx| {
-                    child.draw(origin, child_size.into(), cx);
-                });
-                cx.with_z_index(1, |cx| {
-                    if ix < len - 1 {
-                        Self::push_handle(
-                            self.flexes.clone(),
-                            state.clone(),
-                            self.axis,
-                            ix,
-                            child_bounds,
-                            bounds,
-                            cx,
-                        );
-                    }
-                });
-
-                origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
-            }
-
-            cx.with_z_index(1, |cx| {
-                cx.on_mouse_event({
-                    let state = state.clone();
-                    move |_: &MouseUpEvent, phase, _cx| {
-                        if phase.bubble() {
-                            state.replace(None);
-                        }
-                    }
-                });
-            })
-        }
-    }
-
-    impl ParentElement for PaneAxisElement {
-        fn children_mut(&mut self) -> &mut smallvec::SmallVec<[AnyElement; 2]> {
-            &mut self.children
-        }
-    }
-
-    fn flex_values_in_bounds(flexes: &[f32]) -> bool {
-        (flexes.iter().copied().sum::<f32>() - flexes.len() as f32).abs() < 0.001
-    }
-}

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

@@ -1,973 +0,0 @@
-//#![allow(dead_code)]
-
-pub mod model;
-
-use std::path::Path;
-
-use anyhow::{anyhow, bail, Context, Result};
-use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
-use gpui::{Axis, WindowBounds};
-
-use util::{unzip_option, ResultExt};
-use uuid::Uuid;
-
-use crate::WorkspaceId;
-
-use model::{
-    GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
-    WorkspaceLocation,
-};
-
-use self::model::DockStructure;
-
-define_connection! {
-    // Current schema shape using pseudo-rust syntax:
-    //
-    // workspaces(
-    //   workspace_id: usize, // Primary key for workspaces
-    //   workspace_location: Bincode<Vec<PathBuf>>,
-    //   dock_visible: bool, // Deprecated
-    //   dock_anchor: DockAnchor, // Deprecated
-    //   dock_pane: Option<usize>, // Deprecated
-    //   left_sidebar_open: boolean,
-    //   timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
-    //   window_state: String, // WindowBounds Discriminant
-    //   window_x: Option<f32>, // WindowBounds::Fixed RectF x
-    //   window_y: Option<f32>, // WindowBounds::Fixed RectF y
-    //   window_width: Option<f32>, // WindowBounds::Fixed RectF width
-    //   window_height: Option<f32>, // WindowBounds::Fixed RectF height
-    //   display: Option<Uuid>, // Display id
-    // )
-    //
-    // pane_groups(
-    //   group_id: usize, // Primary key for pane_groups
-    //   workspace_id: usize, // References workspaces table
-    //   parent_group_id: Option<usize>, // None indicates that this is the root node
-    //   position: Optiopn<usize>, // None indicates that this is the root node
-    //   axis: Option<Axis>, // 'Vertical', 'Horizontal'
-    //   flexes: Option<Vec<f32>>, // A JSON array of floats
-    // )
-    //
-    // panes(
-    //     pane_id: usize, // Primary key for panes
-    //     workspace_id: usize, // References workspaces table
-    //     active: bool,
-    // )
-    //
-    // center_panes(
-    //     pane_id: usize, // Primary key for center_panes
-    //     parent_group_id: Option<usize>, // References pane_groups. If none, this is the root
-    //     position: Option<usize>, // None indicates this is the root
-    // )
-    //
-    // CREATE TABLE items(
-    //     item_id: usize, // This is the item's view id, so this is not unique
-    //     workspace_id: usize, // References workspaces table
-    //     pane_id: usize, // References panes table
-    //     kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global
-    //     position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column
-    //     active: bool, // Indicates if this item is the active one in the pane
-    // )
-    pub static ref DB: WorkspaceDb<()> =
-    &[sql!(
-        CREATE TABLE workspaces(
-            workspace_id INTEGER PRIMARY KEY,
-            workspace_location BLOB UNIQUE,
-            dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
-            dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
-            dock_pane INTEGER, // Deprecated.  Preserving so users can downgrade Zed.
-            left_sidebar_open INTEGER, // Boolean
-            timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
-            FOREIGN KEY(dock_pane) REFERENCES panes(pane_id)
-        ) STRICT;
-
-        CREATE TABLE pane_groups(
-            group_id INTEGER PRIMARY KEY,
-            workspace_id INTEGER NOT NULL,
-            parent_group_id INTEGER, // NULL indicates that this is a root node
-            position INTEGER, // NULL indicates that this is a root node
-            axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal'
-            FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-            ON DELETE CASCADE
-            ON UPDATE CASCADE,
-            FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
-        ) STRICT;
-
-        CREATE TABLE panes(
-            pane_id INTEGER PRIMARY KEY,
-            workspace_id INTEGER NOT NULL,
-            active INTEGER NOT NULL, // Boolean
-            FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-            ON DELETE CASCADE
-            ON UPDATE CASCADE
-        ) STRICT;
-
-        CREATE TABLE center_panes(
-            pane_id INTEGER PRIMARY KEY,
-            parent_group_id INTEGER, // NULL means that this is a root pane
-            position INTEGER, // NULL means that this is a root pane
-            FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
-            ON DELETE CASCADE,
-            FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE
-        ) STRICT;
-
-        CREATE TABLE items(
-            item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique
-            workspace_id INTEGER NOT NULL,
-            pane_id INTEGER NOT NULL,
-            kind TEXT NOT NULL,
-            position INTEGER NOT NULL,
-            active INTEGER NOT NULL,
-            FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-            ON DELETE CASCADE
-            ON UPDATE CASCADE,
-            FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
-            ON DELETE CASCADE,
-            PRIMARY KEY(item_id, workspace_id)
-        ) STRICT;
-    ),
-    sql!(
-        ALTER TABLE workspaces ADD COLUMN window_state TEXT;
-        ALTER TABLE workspaces ADD COLUMN window_x REAL;
-        ALTER TABLE workspaces ADD COLUMN window_y REAL;
-        ALTER TABLE workspaces ADD COLUMN window_width REAL;
-        ALTER TABLE workspaces ADD COLUMN window_height REAL;
-        ALTER TABLE workspaces ADD COLUMN display BLOB;
-    ),
-    // Drop foreign key constraint from workspaces.dock_pane to panes table.
-    sql!(
-        CREATE TABLE workspaces_2(
-            workspace_id INTEGER PRIMARY KEY,
-            workspace_location BLOB UNIQUE,
-            dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
-            dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
-            dock_pane INTEGER, // Deprecated.  Preserving so users can downgrade Zed.
-            left_sidebar_open INTEGER, // Boolean
-            timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
-            window_state TEXT,
-            window_x REAL,
-            window_y REAL,
-            window_width REAL,
-            window_height REAL,
-            display BLOB
-        ) STRICT;
-        INSERT INTO workspaces_2 SELECT * FROM workspaces;
-        DROP TABLE workspaces;
-        ALTER TABLE workspaces_2 RENAME TO workspaces;
-    ),
-    // Add panels related information
-    sql!(
-        ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool
-        ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT;
-        ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool
-        ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT;
-        ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool
-        ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT;
-    ),
-    // Add panel zoom persistence
-    sql!(
-        ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool
-        ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool
-        ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool
-    ),
-    // Add pane group flex data
-    sql!(
-        ALTER TABLE pane_groups ADD COLUMN flexes TEXT;
-    )
-    ];
-}
-
-impl WorkspaceDb {
-    /// Returns a serialized workspace for the given worktree_roots. If the passed array
-    /// is empty, the most recent workspace is returned instead. If no workspace for the
-    /// passed roots is stored, returns none.
-    pub fn workspace_for_roots<P: AsRef<Path>>(
-        &self,
-        worktree_roots: &[P],
-    ) -> Option<SerializedWorkspace> {
-        let workspace_location: WorkspaceLocation = worktree_roots.into();
-
-        // Note that we re-assign the workspace_id here in case it's empty
-        // and we've grabbed the most recent workspace
-        let (workspace_id, workspace_location, bounds, display, docks): (
-            WorkspaceId,
-            WorkspaceLocation,
-            Option<WindowBounds>,
-            Option<Uuid>,
-            DockStructure,
-        ) = self
-            .select_row_bound(sql! {
-                SELECT
-                    workspace_id,
-                    workspace_location,
-                    window_state,
-                    window_x,
-                    window_y,
-                    window_width,
-                    window_height,
-                    display,
-                    left_dock_visible,
-                    left_dock_active_panel,
-                    left_dock_zoom,
-                    right_dock_visible,
-                    right_dock_active_panel,
-                    right_dock_zoom,
-                    bottom_dock_visible,
-                    bottom_dock_active_panel,
-                    bottom_dock_zoom
-                FROM workspaces
-                WHERE workspace_location = ?
-            })
-            .and_then(|mut prepared_statement| (prepared_statement)(&workspace_location))
-            .context("No workspaces found")
-            .warn_on_err()
-            .flatten()?;
-
-        Some(SerializedWorkspace {
-            id: workspace_id,
-            location: workspace_location.clone(),
-            center_group: self
-                .get_center_pane_group(workspace_id)
-                .context("Getting center group")
-                .log_err()?,
-            bounds,
-            display,
-            docks,
-        })
-    }
-
-    /// Saves a workspace using the worktree roots. Will garbage collect any workspaces
-    /// that used this workspace previously
-    pub async fn save_workspace(&self, workspace: SerializedWorkspace) {
-        self.write(move |conn| {
-            conn.with_savepoint("update_worktrees", || {
-                // Clear out panes and pane_groups
-                conn.exec_bound(sql!(
-                    DELETE FROM pane_groups WHERE workspace_id = ?1;
-                    DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id)
-                .expect("Clearing old panes");
-
-                conn.exec_bound(sql!(
-                    DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ?
-                ))?((&workspace.location, workspace.id.clone()))
-                .context("clearing out old locations")?;
-
-                // Upsert
-                conn.exec_bound(sql!(
-                    INSERT INTO workspaces(
-                        workspace_id,
-                        workspace_location,
-                        left_dock_visible,
-                        left_dock_active_panel,
-                        left_dock_zoom,
-                        right_dock_visible,
-                        right_dock_active_panel,
-                        right_dock_zoom,
-                        bottom_dock_visible,
-                        bottom_dock_active_panel,
-                        bottom_dock_zoom,
-                        timestamp
-                    )
-                    VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
-                    ON CONFLICT DO
-                    UPDATE SET
-                        workspace_location = ?2,
-                        left_dock_visible = ?3,
-                        left_dock_active_panel = ?4,
-                        left_dock_zoom = ?5,
-                        right_dock_visible = ?6,
-                        right_dock_active_panel = ?7,
-                        right_dock_zoom = ?8,
-                        bottom_dock_visible = ?9,
-                        bottom_dock_active_panel = ?10,
-                        bottom_dock_zoom = ?11,
-                        timestamp = CURRENT_TIMESTAMP
-                ))?((workspace.id, &workspace.location, workspace.docks))
-                .context("Updating workspace")?;
-
-                // Save center pane group
-                Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
-                    .context("save pane group in save workspace")?;
-
-                Ok(())
-            })
-            .log_err();
-        })
-        .await;
-    }
-
-    query! {
-        pub async fn next_id() -> Result<WorkspaceId> {
-            INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
-        }
-    }
-
-    query! {
-        fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
-            SELECT workspace_id, workspace_location
-            FROM workspaces
-            WHERE workspace_location IS NOT NULL
-            ORDER BY timestamp DESC
-        }
-    }
-
-    query! {
-        async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> {
-            DELETE FROM workspaces
-            WHERE workspace_id IS ?
-        }
-    }
-
-    // Returns the recent locations which are still valid on disk and deletes ones which no longer
-    // exist.
-    pub async fn recent_workspaces_on_disk(&self) -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
-        let mut result = Vec::new();
-        let mut delete_tasks = Vec::new();
-        for (id, location) in self.recent_workspaces()? {
-            if location.paths().iter().all(|path| path.exists())
-                && location.paths().iter().any(|path| path.is_dir())
-            {
-                result.push((id, location));
-            } else {
-                delete_tasks.push(self.delete_stale_workspace(id));
-            }
-        }
-
-        futures::future::join_all(delete_tasks).await;
-        Ok(result)
-    }
-
-    pub async fn last_workspace(&self) -> Result<Option<WorkspaceLocation>> {
-        Ok(self
-            .recent_workspaces_on_disk()
-            .await?
-            .into_iter()
-            .next()
-            .map(|(_, location)| location))
-    }
-
-    fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
-        Ok(self
-            .get_pane_group(workspace_id, None)?
-            .into_iter()
-            .next()
-            .unwrap_or_else(|| {
-                SerializedPaneGroup::Pane(SerializedPane {
-                    active: true,
-                    children: vec![],
-                })
-            }))
-    }
-
-    fn get_pane_group(
-        &self,
-        workspace_id: WorkspaceId,
-        group_id: Option<GroupId>,
-    ) -> Result<Vec<SerializedPaneGroup>> {
-        type GroupKey = (Option<GroupId>, WorkspaceId);
-        type GroupOrPane = (
-            Option<GroupId>,
-            Option<Axis>,
-            Option<PaneId>,
-            Option<bool>,
-            Option<String>,
-        );
-        self.select_bound::<GroupKey, GroupOrPane>(sql!(
-            SELECT group_id, axis, pane_id, active, flexes
-                FROM (SELECT
-                        group_id,
-                        axis,
-                        NULL as pane_id,
-                        NULL as active,
-                        position,
-                        parent_group_id,
-                        workspace_id,
-                        flexes
-                      FROM pane_groups
-                    UNION
-                      SELECT
-                        NULL,
-                        NULL,
-                        center_panes.pane_id,
-                        panes.active as active,
-                        position,
-                        parent_group_id,
-                        panes.workspace_id as workspace_id,
-                        NULL
-                      FROM center_panes
-                      JOIN panes ON center_panes.pane_id = panes.pane_id)
-                WHERE parent_group_id IS ? AND workspace_id = ?
-                ORDER BY position
-        ))?((group_id, workspace_id))?
-        .into_iter()
-        .map(|(group_id, axis, pane_id, active, flexes)| {
-            if let Some((group_id, axis)) = group_id.zip(axis) {
-                let flexes = flexes
-                    .map(|flexes: String| serde_json::from_str::<Vec<f32>>(&flexes))
-                    .transpose()?;
-
-                Ok(SerializedPaneGroup::Group {
-                    axis,
-                    children: self.get_pane_group(workspace_id, Some(group_id))?,
-                    flexes,
-                })
-            } else if let Some((pane_id, active)) = pane_id.zip(active) {
-                Ok(SerializedPaneGroup::Pane(SerializedPane::new(
-                    self.get_items(pane_id)?,
-                    active,
-                )))
-            } else {
-                bail!("Pane Group Child was neither a pane group or a pane");
-            }
-        })
-        // Filter out panes and pane groups which don't have any children or items
-        .filter(|pane_group| match pane_group {
-            Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
-            Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
-            _ => true,
-        })
-        .collect::<Result<_>>()
-    }
-
-    fn save_pane_group(
-        conn: &Connection,
-        workspace_id: WorkspaceId,
-        pane_group: &SerializedPaneGroup,
-        parent: Option<(GroupId, usize)>,
-    ) -> Result<()> {
-        match pane_group {
-            SerializedPaneGroup::Group {
-                axis,
-                children,
-                flexes,
-            } => {
-                let (parent_id, position) = unzip_option(parent);
-
-                let flex_string = flexes
-                    .as_ref()
-                    .map(|flexes| serde_json::json!(flexes).to_string());
-
-                let group_id = conn.select_row_bound::<_, i64>(sql!(
-                    INSERT INTO pane_groups(
-                        workspace_id,
-                        parent_group_id,
-                        position,
-                        axis,
-                        flexes
-                    )
-                    VALUES (?, ?, ?, ?, ?)
-                    RETURNING group_id
-                ))?((
-                    workspace_id,
-                    parent_id,
-                    position,
-                    *axis,
-                    flex_string,
-                ))?
-                .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?;
-
-                for (position, group) in children.iter().enumerate() {
-                    Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))?
-                }
-
-                Ok(())
-            }
-            SerializedPaneGroup::Pane(pane) => {
-                Self::save_pane(conn, workspace_id, &pane, parent)?;
-                Ok(())
-            }
-        }
-    }
-
-    fn save_pane(
-        conn: &Connection,
-        workspace_id: WorkspaceId,
-        pane: &SerializedPane,
-        parent: Option<(GroupId, usize)>,
-    ) -> Result<PaneId> {
-        let pane_id = conn.select_row_bound::<_, i64>(sql!(
-            INSERT INTO panes(workspace_id, active)
-            VALUES (?, ?)
-            RETURNING pane_id
-        ))?((workspace_id, pane.active))?
-        .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
-
-        let (parent_id, order) = unzip_option(parent);
-        conn.exec_bound(sql!(
-            INSERT INTO center_panes(pane_id, parent_group_id, position)
-            VALUES (?, ?, ?)
-        ))?((pane_id, parent_id, order))?;
-
-        Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?;
-
-        Ok(pane_id)
-    }
-
-    fn get_items(&self, pane_id: PaneId) -> Result<Vec<SerializedItem>> {
-        Ok(self.select_bound(sql!(
-            SELECT kind, item_id, active FROM items
-            WHERE pane_id = ?
-                ORDER BY position
-        ))?(pane_id)?)
-    }
-
-    fn save_items(
-        conn: &Connection,
-        workspace_id: WorkspaceId,
-        pane_id: PaneId,
-        items: &[SerializedItem],
-    ) -> Result<()> {
-        let mut insert = conn.exec_bound(sql!(
-            INSERT INTO items(workspace_id, pane_id, position, kind, item_id, active) VALUES (?, ?, ?, ?, ?, ?)
-        )).context("Preparing insertion")?;
-        for (position, item) in items.iter().enumerate() {
-            insert((workspace_id, pane_id, position, item))?;
-        }
-
-        Ok(())
-    }
-
-    query! {
-        pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> {
-            UPDATE workspaces
-            SET timestamp = CURRENT_TIMESTAMP
-            WHERE workspace_id = ?
-        }
-    }
-
-    query! {
-        pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> {
-            UPDATE workspaces
-            SET window_state = ?2,
-                window_x = ?3,
-                window_y = ?4,
-                window_width = ?5,
-                window_height = ?6,
-                display = ?7
-            WHERE workspace_id = ?1
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use db::open_test_db;
-    use gpui;
-
-    #[gpui::test]
-    async fn test_next_id_stability() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_next_id_stability").await);
-
-        db.write(|conn| {
-            conn.migrate(
-                "test_table",
-                &[sql!(
-                    CREATE TABLE test_table(
-                        text TEXT,
-                        workspace_id INTEGER,
-                        FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
-                        ON DELETE CASCADE
-                    ) STRICT;
-                )],
-            )
-            .unwrap();
-        })
-        .await;
-
-        let id = db.next_id().await.unwrap();
-        // Assert the empty row got inserted
-        assert_eq!(
-            Some(id),
-            db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
-                SELECT workspace_id FROM workspaces WHERE workspace_id = ?
-            ))
-            .unwrap()(id)
-            .unwrap()
-        );
-
-        db.write(move |conn| {
-            conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
-                .unwrap()(("test-text-1", id))
-            .unwrap()
-        })
-        .await;
-
-        let test_text_1 = db
-            .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
-            .unwrap()(1)
-        .unwrap()
-        .unwrap();
-        assert_eq!(test_text_1, "test-text-1");
-    }
-
-    #[gpui::test]
-    async fn test_workspace_id_stability() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await);
-
-        db.write(|conn| {
-            conn.migrate(
-                "test_table",
-                &[sql!(
-                        CREATE TABLE test_table(
-                            text TEXT,
-                            workspace_id INTEGER,
-                            FOREIGN KEY(workspace_id)
-                                REFERENCES workspaces(workspace_id)
-                            ON DELETE CASCADE
-                        ) STRICT;)],
-            )
-        })
-        .await
-        .unwrap();
-
-        let mut workspace_1 = SerializedWorkspace {
-            id: 1,
-            location: (["/tmp", "/tmp2"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        let workspace_2 = SerializedWorkspace {
-            id: 2,
-            location: (["/tmp"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace_1.clone()).await;
-
-        db.write(|conn| {
-            conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
-                .unwrap()(("test-text-1", 1))
-            .unwrap();
-        })
-        .await;
-
-        db.save_workspace(workspace_2.clone()).await;
-
-        db.write(|conn| {
-            conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?)))
-                .unwrap()(("test-text-2", 2))
-            .unwrap();
-        })
-        .await;
-
-        workspace_1.location = (["/tmp", "/tmp3"]).into();
-        db.save_workspace(workspace_1.clone()).await;
-        db.save_workspace(workspace_1).await;
-        db.save_workspace(workspace_2).await;
-
-        let test_text_2 = db
-            .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
-            .unwrap()(2)
-        .unwrap()
-        .unwrap();
-        assert_eq!(test_text_2, "test-text-2");
-
-        let test_text_1 = db
-            .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
-            .unwrap()(1)
-        .unwrap()
-        .unwrap();
-        assert_eq!(test_text_1, "test-text-1");
-    }
-
-    fn group(axis: Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
-        SerializedPaneGroup::Group {
-            axis,
-            flexes: None,
-            children,
-        }
-    }
-
-    #[gpui::test]
-    async fn test_full_workspace_serialization() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
-
-        //  -----------------
-        //  | 1,2   | 5,6   |
-        //  | - - - |       |
-        //  | 3,4   |       |
-        //  -----------------
-        let center_group = group(
-            Axis::Horizontal,
-            vec![
-                group(
-                    Axis::Vertical,
-                    vec![
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 5, false),
-                                SerializedItem::new("Terminal", 6, true),
-                            ],
-                            false,
-                        )),
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 7, true),
-                                SerializedItem::new("Terminal", 8, false),
-                            ],
-                            false,
-                        )),
-                    ],
-                ),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 9, false),
-                        SerializedItem::new("Terminal", 10, true),
-                    ],
-                    false,
-                )),
-            ],
-        );
-
-        let workspace = SerializedWorkspace {
-            id: 5,
-            location: (["/tmp", "/tmp2"]).into(),
-            center_group,
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace.clone()).await;
-        let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]);
-
-        assert_eq!(workspace, round_trip_workspace.unwrap());
-
-        // Test guaranteed duplicate IDs
-        db.save_workspace(workspace.clone()).await;
-        db.save_workspace(workspace.clone()).await;
-
-        let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]);
-        assert_eq!(workspace, round_trip_workspace.unwrap());
-    }
-
-    #[gpui::test]
-    async fn test_workspace_assignment() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_basic_functionality").await);
-
-        let workspace_1 = SerializedWorkspace {
-            id: 1,
-            location: (["/tmp", "/tmp2"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        let mut workspace_2 = SerializedWorkspace {
-            id: 2,
-            location: (["/tmp"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace_1.clone()).await;
-        db.save_workspace(workspace_2.clone()).await;
-
-        // Test that paths are treated as a set
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
-            workspace_1
-        );
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(),
-            workspace_1
-        );
-
-        // Make sure that other keys work
-        assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2);
-        assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None);
-
-        // Test 'mutate' case of updating a pre-existing id
-        workspace_2.location = (["/tmp", "/tmp2"]).into();
-
-        db.save_workspace(workspace_2.clone()).await;
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
-            workspace_2
-        );
-
-        // Test other mechanism for mutating
-        let mut workspace_3 = SerializedWorkspace {
-            id: 3,
-            location: (&["/tmp", "/tmp2"]).into(),
-            center_group: Default::default(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        };
-
-        db.save_workspace(workspace_3.clone()).await;
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(),
-            workspace_3
-        );
-
-        // Make sure that updating paths differently also works
-        workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into();
-        db.save_workspace(workspace_3.clone()).await;
-        assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
-        assert_eq!(
-            db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
-                .unwrap(),
-            workspace_3
-        );
-    }
-
-    use crate::persistence::model::SerializedWorkspace;
-    use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
-
-    fn default_workspace<P: AsRef<Path>>(
-        workspace_id: &[P],
-        center_group: &SerializedPaneGroup,
-    ) -> SerializedWorkspace {
-        SerializedWorkspace {
-            id: 4,
-            location: workspace_id.into(),
-            center_group: center_group.clone(),
-            bounds: Default::default(),
-            display: Default::default(),
-            docks: Default::default(),
-        }
-    }
-
-    #[gpui::test]
-    async fn test_simple_split() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("simple_split").await);
-
-        //  -----------------
-        //  | 1,2   | 5,6   |
-        //  | - - - |       |
-        //  | 3,4   |       |
-        //  -----------------
-        let center_pane = group(
-            Axis::Horizontal,
-            vec![
-                group(
-                    Axis::Vertical,
-                    vec![
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 1, false),
-                                SerializedItem::new("Terminal", 2, true),
-                            ],
-                            false,
-                        )),
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 4, false),
-                                SerializedItem::new("Terminal", 3, true),
-                            ],
-                            true,
-                        )),
-                    ],
-                ),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 5, true),
-                        SerializedItem::new("Terminal", 6, false),
-                    ],
-                    false,
-                )),
-            ],
-        );
-
-        let workspace = default_workspace(&["/tmp"], &center_pane);
-
-        db.save_workspace(workspace.clone()).await;
-
-        let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap();
-
-        assert_eq!(workspace.center_group, new_workspace.center_group);
-    }
-
-    #[gpui::test]
-    async fn test_cleanup_panes() {
-        env_logger::try_init().ok();
-
-        let db = WorkspaceDb(open_test_db("test_cleanup_panes").await);
-
-        let center_pane = group(
-            Axis::Horizontal,
-            vec![
-                group(
-                    Axis::Vertical,
-                    vec![
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 1, false),
-                                SerializedItem::new("Terminal", 2, true),
-                            ],
-                            false,
-                        )),
-                        SerializedPaneGroup::Pane(SerializedPane::new(
-                            vec![
-                                SerializedItem::new("Terminal", 4, false),
-                                SerializedItem::new("Terminal", 3, true),
-                            ],
-                            true,
-                        )),
-                    ],
-                ),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 5, false),
-                        SerializedItem::new("Terminal", 6, true),
-                    ],
-                    false,
-                )),
-            ],
-        );
-
-        let id = &["/tmp"];
-
-        let mut workspace = default_workspace(id, &center_pane);
-
-        db.save_workspace(workspace.clone()).await;
-
-        workspace.center_group = group(
-            Axis::Vertical,
-            vec![
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 1, false),
-                        SerializedItem::new("Terminal", 2, true),
-                    ],
-                    false,
-                )),
-                SerializedPaneGroup::Pane(SerializedPane::new(
-                    vec![
-                        SerializedItem::new("Terminal", 4, true),
-                        SerializedItem::new("Terminal", 3, false),
-                    ],
-                    true,
-                )),
-            ],
-        );
-
-        db.save_workspace(workspace.clone()).await;
-
-        let new_workspace = db.workspace_for_roots(id).unwrap();
-
-        assert_eq!(workspace.center_group, new_workspace.center_group);
-    }
-}

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

@@ -1,335 +0,0 @@
-use crate::{item::ItemHandle, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId};
-use anyhow::{Context, Result};
-use async_recursion::async_recursion;
-use db::sqlez::{
-    bindable::{Bind, Column, StaticColumnCount},
-    statement::Statement,
-};
-use gpui::{AsyncWindowContext, Axis, Model, Task, View, WeakView, WindowBounds};
-use project::Project;
-use std::{
-    path::{Path, PathBuf},
-    sync::Arc,
-};
-use util::ResultExt;
-use uuid::Uuid;
-
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
-
-impl WorkspaceLocation {
-    pub fn paths(&self) -> Arc<Vec<PathBuf>> {
-        self.0.clone()
-    }
-}
-
-impl<P: AsRef<Path>, T: IntoIterator<Item = P>> From<T> for WorkspaceLocation {
-    fn from(iterator: T) -> Self {
-        let mut roots = iterator
-            .into_iter()
-            .map(|p| p.as_ref().to_path_buf())
-            .collect::<Vec<_>>();
-        roots.sort();
-        Self(Arc::new(roots))
-    }
-}
-
-impl StaticColumnCount for WorkspaceLocation {}
-impl Bind for &WorkspaceLocation {
-    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
-        bincode::serialize(&self.0)
-            .expect("Bincode serialization of paths should not fail")
-            .bind(statement, start_index)
-    }
-}
-
-impl Column for WorkspaceLocation {
-    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
-        let blob = statement.column_blob(start_index)?;
-        Ok((
-            WorkspaceLocation(bincode::deserialize(blob).context("Bincode failed")?),
-            start_index + 1,
-        ))
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub struct SerializedWorkspace {
-    pub id: WorkspaceId,
-    pub location: WorkspaceLocation,
-    pub center_group: SerializedPaneGroup,
-    pub bounds: Option<WindowBounds>,
-    pub display: Option<Uuid>,
-    pub docks: DockStructure,
-}
-
-#[derive(Debug, PartialEq, Clone, Default)]
-pub struct DockStructure {
-    pub(crate) left: DockData,
-    pub(crate) right: DockData,
-    pub(crate) bottom: DockData,
-}
-
-impl Column for DockStructure {
-    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
-        let (left, next_index) = DockData::column(statement, start_index)?;
-        let (right, next_index) = DockData::column(statement, next_index)?;
-        let (bottom, next_index) = DockData::column(statement, next_index)?;
-        Ok((
-            DockStructure {
-                left,
-                right,
-                bottom,
-            },
-            next_index,
-        ))
-    }
-}
-
-impl Bind for DockStructure {
-    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
-        let next_index = statement.bind(&self.left, start_index)?;
-        let next_index = statement.bind(&self.right, next_index)?;
-        statement.bind(&self.bottom, next_index)
-    }
-}
-
-#[derive(Debug, PartialEq, Clone, Default)]
-pub struct DockData {
-    pub(crate) visible: bool,
-    pub(crate) active_panel: Option<String>,
-    pub(crate) zoom: bool,
-}
-
-impl Column for DockData {
-    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
-        let (visible, next_index) = Option::<bool>::column(statement, start_index)?;
-        let (active_panel, next_index) = Option::<String>::column(statement, next_index)?;
-        let (zoom, next_index) = Option::<bool>::column(statement, next_index)?;
-        Ok((
-            DockData {
-                visible: visible.unwrap_or(false),
-                active_panel,
-                zoom: zoom.unwrap_or(false),
-            },
-            next_index,
-        ))
-    }
-}
-
-impl Bind for DockData {
-    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
-        let next_index = statement.bind(&self.visible, start_index)?;
-        let next_index = statement.bind(&self.active_panel, next_index)?;
-        statement.bind(&self.zoom, next_index)
-    }
-}
-
-#[derive(Debug, PartialEq, Clone)]
-pub enum SerializedPaneGroup {
-    Group {
-        axis: Axis,
-        flexes: Option<Vec<f32>>,
-        children: Vec<SerializedPaneGroup>,
-    },
-    Pane(SerializedPane),
-}
-
-#[cfg(test)]
-impl Default for SerializedPaneGroup {
-    fn default() -> Self {
-        Self::Pane(SerializedPane {
-            children: vec![SerializedItem::default()],
-            active: false,
-        })
-    }
-}
-
-impl SerializedPaneGroup {
-    #[async_recursion(?Send)]
-    pub(crate) async fn deserialize(
-        self,
-        project: &Model<Project>,
-        workspace_id: WorkspaceId,
-        workspace: WeakView<Workspace>,
-        cx: &mut AsyncWindowContext,
-    ) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
-        match self {
-            SerializedPaneGroup::Group {
-                axis,
-                children,
-                flexes,
-            } => {
-                let mut current_active_pane = None;
-                let mut members = Vec::new();
-                let mut items = Vec::new();
-                for child in children {
-                    if let Some((new_member, active_pane, new_items)) = child
-                        .deserialize(project, workspace_id, workspace.clone(), cx)
-                        .await
-                    {
-                        members.push(new_member);
-                        items.extend(new_items);
-                        current_active_pane = current_active_pane.or(active_pane);
-                    }
-                }
-
-                if members.is_empty() {
-                    return None;
-                }
-
-                if members.len() == 1 {
-                    return Some((members.remove(0), current_active_pane, items));
-                }
-
-                Some((
-                    Member::Axis(PaneAxis::load(axis, members, flexes)),
-                    current_active_pane,
-                    items,
-                ))
-            }
-            SerializedPaneGroup::Pane(serialized_pane) => {
-                let pane = workspace
-                    .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
-                    .log_err()?;
-                let active = serialized_pane.active;
-                let new_items = serialized_pane
-                    .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
-                    .await
-                    .log_err()?;
-
-                if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
-                    let pane = pane.upgrade()?;
-                    Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
-                } else {
-                    let pane = pane.upgrade()?;
-                    workspace
-                        .update(cx, |workspace, cx| workspace.force_remove_pane(&pane, cx))
-                        .log_err()?;
-                    None
-                }
-            }
-        }
-    }
-}
-
-#[derive(Debug, PartialEq, Eq, Default, Clone)]
-pub struct SerializedPane {
-    pub(crate) active: bool,
-    pub(crate) children: Vec<SerializedItem>,
-}
-
-impl SerializedPane {
-    pub fn new(children: Vec<SerializedItem>, active: bool) -> Self {
-        SerializedPane { children, active }
-    }
-
-    pub async fn deserialize_to(
-        &self,
-        project: &Model<Project>,
-        pane: &WeakView<Pane>,
-        workspace_id: WorkspaceId,
-        workspace: WeakView<Workspace>,
-        cx: &mut AsyncWindowContext,
-    ) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
-        let mut items = Vec::new();
-        let mut active_item_index = None;
-        for (index, item) in self.children.iter().enumerate() {
-            let project = project.clone();
-            let item_handle = pane
-                .update(cx, |_, cx| {
-                    if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
-                        deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
-                    } else {
-                        Task::ready(Err(anyhow::anyhow!(
-                            "Deserializer does not exist for item kind: {}",
-                            item.kind
-                        )))
-                    }
-                })?
-                .await
-                .log_err();
-
-            items.push(item_handle.clone());
-
-            if let Some(item_handle) = item_handle {
-                pane.update(cx, |pane, cx| {
-                    pane.add_item(item_handle.clone(), true, true, None, cx);
-                })?;
-            }
-
-            if item.active {
-                active_item_index = Some(index);
-            }
-        }
-
-        if let Some(active_item_index) = active_item_index {
-            pane.update(cx, |pane, cx| {
-                pane.activate_item(active_item_index, false, false, cx);
-            })?;
-        }
-
-        anyhow::Ok(items)
-    }
-}
-
-pub type GroupId = i64;
-pub type PaneId = i64;
-pub type ItemId = u64;
-
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub struct SerializedItem {
-    pub kind: Arc<str>,
-    pub item_id: ItemId,
-    pub active: bool,
-}
-
-impl SerializedItem {
-    pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool) -> Self {
-        Self {
-            kind: Arc::from(kind.as_ref()),
-            item_id,
-            active,
-        }
-    }
-}
-
-#[cfg(test)]
-impl Default for SerializedItem {
-    fn default() -> Self {
-        SerializedItem {
-            kind: Arc::from("Terminal"),
-            item_id: 100000,
-            active: false,
-        }
-    }
-}
-
-impl StaticColumnCount for SerializedItem {
-    fn column_count() -> usize {
-        3
-    }
-}
-impl Bind for &SerializedItem {
-    fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
-        let next_index = statement.bind(&self.kind, start_index)?;
-        let next_index = statement.bind(&self.item_id, next_index)?;
-        statement.bind(&self.active, next_index)
-    }
-}
-
-impl Column for SerializedItem {
-    fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
-        let (kind, next_index) = Arc::<str>::column(statement, start_index)?;
-        let (item_id, next_index) = ItemId::column(statement, next_index)?;
-        let (active, next_index) = bool::column(statement, next_index)?;
-        Ok((
-            SerializedItem {
-                kind,
-                item_id,
-                active,
-            },
-            next_index,
-        ))
-    }
-}

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

@@ -1,277 +0,0 @@
-use std::{any::Any, sync::Arc};
-
-use gpui::{
-    AnyView, AppContext, EventEmitter, Subscription, Task, View, ViewContext, WeakView,
-    WindowContext,
-};
-use project::search::SearchQuery;
-
-use crate::{
-    item::{Item, WeakItemHandle},
-    ItemHandle,
-};
-
-#[derive(Debug)]
-pub enum SearchEvent {
-    MatchesInvalidated,
-    ActiveMatchChanged,
-}
-
-#[derive(Clone, Copy, PartialEq, Eq, Debug)]
-pub enum Direction {
-    Prev,
-    Next,
-}
-
-#[derive(Clone, Copy, Debug, Default)]
-pub struct SearchOptions {
-    pub case: bool,
-    pub word: bool,
-    pub regex: bool,
-    /// Specifies whether the item supports search & replace.
-    pub replacement: bool,
-}
-
-pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
-    type Match: Any + Sync + Send + Clone;
-
-    fn supported_options() -> SearchOptions {
-        SearchOptions {
-            case: true,
-            word: true,
-            regex: true,
-            replacement: true,
-        }
-    }
-
-    fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
-    fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
-    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
-    fn activate_match(
-        &mut self,
-        index: usize,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    );
-    fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
-    fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
-    fn match_index_for_direction(
-        &mut self,
-        matches: &Vec<Self::Match>,
-        current_index: usize,
-        direction: Direction,
-        count: usize,
-        _: &mut ViewContext<Self>,
-    ) -> usize {
-        match direction {
-            Direction::Prev => {
-                let count = count % matches.len();
-                if current_index >= count {
-                    current_index - count
-                } else {
-                    matches.len() - (count - current_index)
-                }
-            }
-            Direction::Next => (current_index + count) % matches.len(),
-        }
-    }
-    fn find_matches(
-        &mut self,
-        query: Arc<SearchQuery>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Self::Match>>;
-    fn active_match_index(
-        &mut self,
-        matches: Vec<Self::Match>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<usize>;
-}
-
-pub trait SearchableItemHandle: ItemHandle {
-    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
-    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
-    fn supported_options(&self) -> SearchOptions;
-    fn subscribe_to_search_events(
-        &self,
-        cx: &mut WindowContext,
-        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
-    ) -> Subscription;
-    fn clear_matches(&self, cx: &mut WindowContext);
-    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
-    fn query_suggestion(&self, cx: &mut WindowContext) -> String;
-    fn activate_match(
-        &self,
-        index: usize,
-        matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut WindowContext,
-    );
-    fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
-    fn replace(&self, _: &Box<dyn Any + Send>, _: &SearchQuery, _: &mut WindowContext);
-    fn match_index_for_direction(
-        &self,
-        matches: &Vec<Box<dyn Any + Send>>,
-        current_index: usize,
-        direction: Direction,
-        count: usize,
-        cx: &mut WindowContext,
-    ) -> usize;
-    fn find_matches(
-        &self,
-        query: Arc<SearchQuery>,
-        cx: &mut WindowContext,
-    ) -> Task<Vec<Box<dyn Any + Send>>>;
-    fn active_match_index(
-        &self,
-        matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut WindowContext,
-    ) -> Option<usize>;
-}
-
-// todo!("here is where we need to use AnyWeakView");
-impl<T: SearchableItem> SearchableItemHandle for View<T> {
-    fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
-        Box::new(self.downgrade())
-    }
-
-    fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
-        Box::new(self.clone())
-    }
-
-    fn supported_options(&self) -> SearchOptions {
-        T::supported_options()
-    }
-
-    fn subscribe_to_search_events(
-        &self,
-        cx: &mut WindowContext,
-        handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
-    ) -> Subscription {
-        cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
-    }
-
-    fn clear_matches(&self, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| this.clear_matches(cx));
-    }
-    fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
-        let matches = downcast_matches(matches);
-        self.update(cx, |this, cx| this.update_matches(matches, cx));
-    }
-    fn query_suggestion(&self, cx: &mut WindowContext) -> String {
-        self.update(cx, |this, cx| this.query_suggestion(cx))
-    }
-    fn activate_match(
-        &self,
-        index: usize,
-        matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut WindowContext,
-    ) {
-        let matches = downcast_matches(matches);
-        self.update(cx, |this, cx| this.activate_match(index, matches, cx));
-    }
-
-    fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
-        let matches = downcast_matches(matches);
-        self.update(cx, |this, cx| this.select_matches(matches, cx));
-    }
-
-    fn match_index_for_direction(
-        &self,
-        matches: &Vec<Box<dyn Any + Send>>,
-        current_index: usize,
-        direction: Direction,
-        count: usize,
-        cx: &mut WindowContext,
-    ) -> usize {
-        let matches = downcast_matches(matches);
-        self.update(cx, |this, cx| {
-            this.match_index_for_direction(&matches, current_index, direction, count, cx)
-        })
-    }
-    fn find_matches(
-        &self,
-        query: Arc<SearchQuery>,
-        cx: &mut WindowContext,
-    ) -> Task<Vec<Box<dyn Any + Send>>> {
-        let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
-        cx.spawn(|_| async {
-            let matches = matches.await;
-            matches
-                .into_iter()
-                .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
-                .collect()
-        })
-    }
-    fn active_match_index(
-        &self,
-        matches: &Vec<Box<dyn Any + Send>>,
-        cx: &mut WindowContext,
-    ) -> Option<usize> {
-        let matches = downcast_matches(matches);
-        self.update(cx, |this, cx| this.active_match_index(matches, cx))
-    }
-
-    fn replace(&self, matches: &Box<dyn Any + Send>, query: &SearchQuery, cx: &mut WindowContext) {
-        let matches = matches.downcast_ref().unwrap();
-        self.update(cx, |this, cx| this.replace(matches, query, cx))
-    }
-}
-
-fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
-    matches
-        .iter()
-        .map(|range| range.downcast_ref::<T>().cloned())
-        .collect::<Option<Vec<_>>>()
-        .expect(
-            "SearchableItemHandle function called with vec of matches of a different type than expected",
-        )
-}
-
-impl From<Box<dyn SearchableItemHandle>> for AnyView {
-    fn from(this: Box<dyn SearchableItemHandle>) -> Self {
-        this.to_any().clone()
-    }
-}
-
-impl From<&Box<dyn SearchableItemHandle>> for AnyView {
-    fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
-        this.to_any().clone()
-    }
-}
-
-impl PartialEq for Box<dyn SearchableItemHandle> {
-    fn eq(&self, other: &Self) -> bool {
-        self.item_id() == other.item_id()
-    }
-}
-
-impl Eq for Box<dyn SearchableItemHandle> {}
-
-pub trait WeakSearchableItemHandle: WeakItemHandle {
-    fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
-
-    // fn into_any(self) -> AnyWeakView;
-}
-
-impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
-    fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
-        Some(Box::new(self.upgrade()?))
-    }
-
-    // fn into_any(self) -> AnyView {
-    //     self.into_any()
-    // }
-}
-
-impl PartialEq for Box<dyn WeakSearchableItemHandle> {
-    fn eq(&self, other: &Self) -> bool {
-        self.id() == other.id()
-    }
-}
-
-impl Eq for Box<dyn WeakSearchableItemHandle> {}
-
-impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.id().hash(state)
-    }
-}

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

@@ -1,128 +0,0 @@
-use crate::{
-    item::{Item, ItemEvent},
-    ItemNavHistory, WorkspaceId,
-};
-use anyhow::Result;
-use call::participant::{Frame, RemoteVideoTrack};
-use client::{proto::PeerId, User};
-use futures::StreamExt;
-use gpui::{
-    div, img, AppContext, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
-    ParentElement, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
-    WindowContext,
-};
-use std::sync::{Arc, Weak};
-use ui::{h_stack, prelude::*, Icon, IconElement, Label};
-
-pub enum Event {
-    Close,
-}
-
-pub struct SharedScreen {
-    track: Weak<RemoteVideoTrack>,
-    frame: Option<Frame>,
-    pub peer_id: PeerId,
-    user: Arc<User>,
-    nav_history: Option<ItemNavHistory>,
-    _maintain_frame: Task<Result<()>>,
-    focus: FocusHandle,
-}
-
-impl SharedScreen {
-    pub fn new(
-        track: &Arc<RemoteVideoTrack>,
-        peer_id: PeerId,
-        user: Arc<User>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        cx.focus_handle();
-        let mut frames = track.frames();
-        Self {
-            track: Arc::downgrade(track),
-            frame: None,
-            peer_id,
-            user,
-            nav_history: Default::default(),
-            _maintain_frame: cx.spawn(|this, mut cx| async move {
-                while let Some(frame) = frames.next().await {
-                    this.update(&mut cx, |this, cx| {
-                        this.frame = Some(frame);
-                        cx.notify();
-                    })?;
-                }
-                this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
-                Ok(())
-            }),
-            focus: cx.focus_handle(),
-        }
-    }
-}
-
-impl EventEmitter<Event> for SharedScreen {}
-
-impl FocusableView for SharedScreen {
-    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
-        self.focus.clone()
-    }
-}
-impl Render for SharedScreen {
-    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
-        div().track_focus(&self.focus).size_full().children(
-            self.frame
-                .as_ref()
-                .map(|frame| img(frame.image()).size_full()),
-        )
-    }
-}
-
-impl Item for SharedScreen {
-    type Event = Event;
-
-    fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
-        Some(format!("{}'s screen", self.user.github_login).into())
-    }
-
-    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
-        if let Some(nav_history) = self.nav_history.as_mut() {
-            nav_history.push::<()>(None, cx);
-        }
-    }
-
-    fn tab_content(
-        &self,
-        _: Option<usize>,
-        selected: bool,
-        _: &WindowContext<'_>,
-    ) -> gpui::AnyElement {
-        h_stack()
-            .gap_1()
-            .child(IconElement::new(Icon::Screen))
-            .child(
-                Label::new(format!("{}'s screen", self.user.github_login)).color(if selected {
-                    Color::Default
-                } else {
-                    Color::Muted
-                }),
-            )
-            .into_any()
-    }
-
-    fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
-        self.nav_history = Some(history);
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: WorkspaceId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<Self>> {
-        let track = self.track.upgrade()?;
-        Some(cx.new_view(|cx| Self::new(&track, self.peer_id, self.user.clone(), cx)))
-    }
-
-    fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
-        match event {
-            Event::Close => f(ItemEvent::CloseItem),
-        }
-    }
-}

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

@@ -1,194 +0,0 @@
-use crate::{ItemHandle, Pane};
-use gpui::{
-    div, AnyView, IntoElement, ParentElement, Render, Styled, Subscription, View, ViewContext,
-    WindowContext,
-};
-use std::any::TypeId;
-use ui::{h_stack, prelude::*};
-use util::ResultExt;
-
-pub trait StatusItemView: Render {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn crate::ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    );
-}
-
-trait StatusItemViewHandle: Send {
-    fn to_any(&self) -> AnyView;
-    fn set_active_pane_item(
-        &self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut WindowContext,
-    );
-    fn item_type(&self) -> TypeId;
-}
-
-pub struct StatusBar {
-    left_items: Vec<Box<dyn StatusItemViewHandle>>,
-    right_items: Vec<Box<dyn StatusItemViewHandle>>,
-    active_pane: View<Pane>,
-    _observe_active_pane: Subscription,
-}
-
-impl Render for StatusBar {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        div()
-            .py_0p5()
-            .px_1()
-            .flex()
-            .items_center()
-            .justify_between()
-            .w_full()
-            .h_8()
-            .bg(cx.theme().colors().status_bar_background)
-            .child(self.render_left_tools(cx))
-            .child(self.render_right_tools(cx))
-    }
-}
-
-impl StatusBar {
-    fn render_left_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
-        h_stack()
-            .items_center()
-            .gap_2()
-            .children(self.left_items.iter().map(|item| item.to_any()))
-    }
-
-    fn render_right_tools(&self, _: &mut ViewContext<Self>) -> impl IntoElement {
-        h_stack()
-            .items_center()
-            .gap_2()
-            .children(self.right_items.iter().rev().map(|item| item.to_any()))
-    }
-}
-
-impl StatusBar {
-    pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
-        let mut this = Self {
-            left_items: Default::default(),
-            right_items: Default::default(),
-            active_pane: active_pane.clone(),
-            _observe_active_pane: cx
-                .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
-        };
-        this.update_active_pane_item(cx);
-        this
-    }
-
-    pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
-    where
-        T: 'static + StatusItemView,
-    {
-        let active_pane_item = self.active_pane.read(cx).active_item();
-        item.set_active_pane_item(active_pane_item.as_deref(), cx);
-
-        self.left_items.push(Box::new(item));
-        cx.notify();
-    }
-
-    pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
-        self.left_items
-            .iter()
-            .chain(self.right_items.iter())
-            .find_map(|item| item.to_any().clone().downcast().log_err())
-    }
-
-    pub fn position_of_item<T>(&self) -> Option<usize>
-    where
-        T: StatusItemView,
-    {
-        for (index, item) in self.left_items.iter().enumerate() {
-            if item.item_type() == TypeId::of::<T>() {
-                return Some(index);
-            }
-        }
-        for (index, item) in self.right_items.iter().enumerate() {
-            if item.item_type() == TypeId::of::<T>() {
-                return Some(index + self.left_items.len());
-            }
-        }
-        return None;
-    }
-
-    pub fn insert_item_after<T>(
-        &mut self,
-        position: usize,
-        item: View<T>,
-        cx: &mut ViewContext<Self>,
-    ) where
-        T: 'static + StatusItemView,
-    {
-        let active_pane_item = self.active_pane.read(cx).active_item();
-        item.set_active_pane_item(active_pane_item.as_deref(), cx);
-
-        if position < self.left_items.len() {
-            self.left_items.insert(position + 1, Box::new(item))
-        } else {
-            self.right_items
-                .insert(position + 1 - self.left_items.len(), Box::new(item))
-        }
-        cx.notify()
-    }
-
-    pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
-        if position < self.left_items.len() {
-            self.left_items.remove(position);
-        } else {
-            self.right_items.remove(position - self.left_items.len());
-        }
-        cx.notify();
-    }
-
-    pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
-    where
-        T: 'static + StatusItemView,
-    {
-        let active_pane_item = self.active_pane.read(cx).active_item();
-        item.set_active_pane_item(active_pane_item.as_deref(), cx);
-
-        self.right_items.push(Box::new(item));
-        cx.notify();
-    }
-
-    pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
-        self.active_pane = active_pane.clone();
-        self._observe_active_pane =
-            cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
-        self.update_active_pane_item(cx);
-    }
-
-    fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
-        let active_pane_item = self.active_pane.read(cx).active_item();
-        for item in self.left_items.iter().chain(&self.right_items) {
-            item.set_active_pane_item(active_pane_item.as_deref(), cx);
-        }
-    }
-}
-
-impl<T: StatusItemView> StatusItemViewHandle for View<T> {
-    fn to_any(&self) -> AnyView {
-        self.clone().into()
-    }
-
-    fn set_active_pane_item(
-        &self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut WindowContext,
-    ) {
-        self.update(cx, |this, cx| {
-            this.set_active_pane_item(active_pane_item, cx)
-        });
-    }
-
-    fn item_type(&self) -> TypeId {
-        TypeId::of::<T>()
-    }
-}
-
-impl From<&dyn StatusItemViewHandle> for AnyView {
-    fn from(val: &dyn StatusItemViewHandle) -> Self {
-        val.to_any().clone()
-    }
-}

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

@@ -1,321 +0,0 @@
-use crate::ItemHandle;
-use gpui::{
-    AnyView, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View, ViewContext,
-    WindowContext,
-};
-use ui::prelude::*;
-use ui::{h_stack, v_stack};
-
-pub enum ToolbarItemEvent {
-    ChangeLocation(ToolbarItemLocation),
-}
-
-pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
-    fn set_active_pane_item(
-        &mut self,
-        active_pane_item: Option<&dyn crate::ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> ToolbarItemLocation;
-
-    fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
-
-    /// Number of times toolbar's height will be repeated to get the effective height.
-    /// Useful when multiple rows one under each other are needed.
-    /// The rows have the same width and act as a whole when reacting to resizes and similar events.
-    fn row_count(&self, _cx: &WindowContext) -> usize {
-        1
-    }
-}
-
-trait ToolbarItemViewHandle: Send {
-    fn id(&self) -> EntityId;
-    fn to_any(&self) -> AnyView;
-    fn set_active_pane_item(
-        &self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut WindowContext,
-    ) -> ToolbarItemLocation;
-    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
-    fn row_count(&self, cx: &WindowContext) -> usize;
-}
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum ToolbarItemLocation {
-    Hidden,
-    PrimaryLeft,
-    PrimaryRight,
-    Secondary,
-}
-
-pub struct Toolbar {
-    active_item: Option<Box<dyn ItemHandle>>,
-    hidden: bool,
-    can_navigate: bool,
-    items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
-}
-
-impl Toolbar {
-    fn has_any_visible_items(&self) -> bool {
-        self.items
-            .iter()
-            .any(|(_item, location)| *location != ToolbarItemLocation::Hidden)
-    }
-
-    fn left_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
-        self.items.iter().filter_map(|(item, location)| {
-            if *location == ToolbarItemLocation::PrimaryLeft {
-                Some(item.as_ref())
-            } else {
-                None
-            }
-        })
-    }
-
-    fn right_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
-        self.items.iter().filter_map(|(item, location)| {
-            if *location == ToolbarItemLocation::PrimaryRight {
-                Some(item.as_ref())
-            } else {
-                None
-            }
-        })
-    }
-
-    fn secondary_items(&self) -> impl Iterator<Item = &dyn ToolbarItemViewHandle> {
-        self.items.iter().filter_map(|(item, location)| {
-            if *location == ToolbarItemLocation::Secondary {
-                Some(item.as_ref())
-            } else {
-                None
-            }
-        })
-    }
-}
-
-impl Render for Toolbar {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        if !self.has_any_visible_items() {
-            return div();
-        }
-
-        let secondary_item = self.secondary_items().next().map(|item| item.to_any());
-
-        let has_left_items = self.left_items().count() > 0;
-        let has_right_items = self.right_items().count() > 0;
-
-        v_stack()
-            .p_2()
-            .when(has_left_items || has_right_items, |this| this.gap_2())
-            .border_b()
-            .border_color(cx.theme().colors().border_variant)
-            .bg(cx.theme().colors().toolbar_background)
-            .child(
-                h_stack()
-                    .justify_between()
-                    .when(has_left_items, |this| {
-                        this.child(
-                            h_stack()
-                                .flex_1()
-                                .justify_start()
-                                .children(self.left_items().map(|item| item.to_any())),
-                        )
-                    })
-                    .when(has_right_items, |this| {
-                        this.child(
-                            h_stack()
-                                .flex_1()
-                                .justify_end()
-                                .children(self.right_items().map(|item| item.to_any())),
-                        )
-                    }),
-            )
-            .children(secondary_item)
-    }
-}
-
-// todo!()
-// impl View for Toolbar {
-//     fn ui_name() -> &'static str {
-//         "Toolbar"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let theme = &theme::current(cx).workspace.toolbar;
-
-//         let mut primary_left_items = Vec::new();
-//         let mut primary_right_items = Vec::new();
-//         let mut secondary_item = None;
-//         let spacing = theme.item_spacing;
-//         let mut primary_items_row_count = 1;
-
-//         for (item, position) in &self.items {
-//             match *position {
-//                 ToolbarItemLocation::Hidden => {}
-
-//                 ToolbarItemLocation::PrimaryLeft { flex } => {
-//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-//                     let left_item = ChildView::new(item.as_any(), cx).aligned();
-//                     if let Some((flex, expanded)) = flex {
-//                         primary_left_items.push(left_item.flex(flex, expanded).into_any());
-//                     } else {
-//                         primary_left_items.push(left_item.into_any());
-//                     }
-//                 }
-
-//                 ToolbarItemLocation::PrimaryRight { flex } => {
-//                     primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
-//                     let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
-//                     if let Some((flex, expanded)) = flex {
-//                         primary_right_items.push(right_item.flex(flex, expanded).into_any());
-//                     } else {
-//                         primary_right_items.push(right_item.into_any());
-//                     }
-//                 }
-
-//                 ToolbarItemLocation::Secondary => {
-//                     secondary_item = Some(
-//                         ChildView::new(item.as_any(), cx)
-//                             .constrained()
-//                             .with_height(theme.height * item.row_count(cx) as f32)
-//                             .into_any(),
-//                     );
-//                 }
-//             }
-//         }
-
-//         let container_style = theme.container;
-//         let height = theme.height * primary_items_row_count as f32;
-
-//         let mut primary_items = Flex::row().with_spacing(spacing);
-//         primary_items.extend(primary_left_items);
-//         primary_items.extend(primary_right_items);
-
-//         let mut toolbar = Flex::column();
-//         if !primary_items.is_empty() {
-//             toolbar.add_child(primary_items.constrained().with_height(height));
-//         }
-//         if let Some(secondary_item) = secondary_item {
-//             toolbar.add_child(secondary_item);
-//         }
-
-//         if toolbar.is_empty() {
-//             toolbar.into_any_named("toolbar")
-//         } else {
-//             toolbar
-//                 .contained()
-//                 .with_style(container_style)
-//                 .into_any_named("toolbar")
-//         }
-//     }
-// }
-
-impl Toolbar {
-    pub fn new() -> Self {
-        Self {
-            active_item: None,
-            items: Default::default(),
-            hidden: false,
-            can_navigate: true,
-        }
-    }
-
-    pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
-        self.can_navigate = can_navigate;
-        cx.notify();
-    }
-
-    pub fn add_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
-    where
-        T: 'static + ToolbarItemView,
-    {
-        let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
-        cx.subscribe(&item, |this, item, event, cx| {
-            if let Some((_, current_location)) =
-                this.items.iter_mut().find(|(i, _)| i.id() == item.id())
-            {
-                match event {
-                    ToolbarItemEvent::ChangeLocation(new_location) => {
-                        if new_location != current_location {
-                            *current_location = *new_location;
-                            cx.notify();
-                        }
-                    }
-                }
-            }
-        })
-        .detach();
-        self.items.push((Box::new(item), location));
-        cx.notify();
-    }
-
-    pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-        self.active_item = item.map(|item| item.boxed_clone());
-        self.hidden = self
-            .active_item
-            .as_ref()
-            .map(|item| !item.show_toolbar(cx))
-            .unwrap_or(false);
-
-        for (toolbar_item, current_location) in self.items.iter_mut() {
-            let new_location = toolbar_item.set_active_pane_item(item, cx);
-            if new_location != *current_location {
-                *current_location = new_location;
-                cx.notify();
-            }
-        }
-    }
-
-    pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
-        for (toolbar_item, _) in self.items.iter_mut() {
-            toolbar_item.focus_changed(focused, cx);
-        }
-    }
-
-    pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
-        self.items
-            .iter()
-            .find_map(|(item, _)| item.to_any().downcast().ok())
-    }
-
-    pub fn hidden(&self) -> bool {
-        self.hidden
-    }
-}
-
-impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
-    fn id(&self) -> EntityId {
-        self.entity_id()
-    }
-
-    fn to_any(&self) -> AnyView {
-        self.clone().into()
-    }
-
-    fn set_active_pane_item(
-        &self,
-        active_pane_item: Option<&dyn ItemHandle>,
-        cx: &mut WindowContext,
-    ) -> ToolbarItemLocation {
-        self.update(cx, |this, cx| {
-            this.set_active_pane_item(active_pane_item, cx)
-        })
-    }
-
-    fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
-        self.update(cx, |this, cx| {
-            this.pane_focus_update(pane_focused, cx);
-            cx.notify();
-        });
-    }
-
-    fn row_count(&self, cx: &WindowContext) -> usize {
-        self.read(cx).row_count(cx)
-    }
-}
-
-// todo!()
-// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
-//     fn from(val: &dyn ToolbarItemViewHandle) -> Self {
-//         val.as_any().clone()
-//     }
-// }

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

@@ -1,5277 +0,0 @@
-pub mod dock;
-pub mod item;
-mod modal_layer;
-pub mod notifications;
-pub mod pane;
-pub mod pane_group;
-mod persistence;
-pub mod searchable;
-pub mod shared_screen;
-mod status_bar;
-mod toolbar;
-mod workspace_settings;
-
-use anyhow::{anyhow, Context as _, Result};
-use call::ActiveCall;
-use client::{
-    proto::{self, PeerId},
-    Client, Status, TelemetrySettings, TypedEnvelope, UserStore,
-};
-use collections::{hash_map, HashMap, HashSet};
-use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
-use futures::{
-    channel::{mpsc, oneshot},
-    future::try_join_all,
-    Future, FutureExt, StreamExt,
-};
-use gpui::{
-    actions, canvas, div, impl_actions, point, size, Action, AnyElement, AnyModel, AnyView,
-    AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, BorrowWindow,
-    Bounds, Context, Div, DragMoveEvent, Element, Entity, EntityId, EventEmitter, FocusHandle,
-    FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
-    ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
-    Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
-    WindowBounds, WindowContext, WindowHandle, WindowOptions,
-};
-use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
-use itertools::Itertools;
-use language::{LanguageRegistry, Rope};
-use lazy_static::lazy_static;
-pub use modal_layer::*;
-use node_runtime::NodeRuntime;
-use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
-pub use pane::*;
-pub use pane_group::*;
-use persistence::DB;
-pub use persistence::{
-    model::{ItemId, SerializedWorkspace, WorkspaceLocation},
-    WorkspaceDb, DB as WORKSPACE_DB,
-};
-use postage::stream::Stream;
-use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
-use serde::Deserialize;
-use settings::Settings;
-use shared_screen::SharedScreen;
-use status_bar::StatusBar;
-pub use status_bar::StatusItemView;
-use std::{
-    any::TypeId,
-    borrow::Cow,
-    cmp, env,
-    path::{Path, PathBuf},
-    sync::{atomic::AtomicUsize, Arc},
-    time::Duration,
-};
-use theme::{ActiveTheme, ThemeSettings};
-pub use toolbar::{Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
-pub use ui;
-use ui::Label;
-use util::ResultExt;
-use uuid::Uuid;
-pub use workspace_settings::{AutosaveSetting, WorkspaceSettings};
-
-use crate::persistence::model::{
-    DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
-};
-
-lazy_static! {
-    static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
-        .ok()
-        .as_deref()
-        .and_then(parse_pixel_size_env_var);
-    static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
-        .ok()
-        .as_deref()
-        .and_then(parse_pixel_position_env_var);
-}
-
-#[derive(Clone, PartialEq)]
-pub struct RemoveWorktreeFromProject(pub WorktreeId);
-
-actions!(
-    workspace,
-    [
-        Open,
-        NewFile,
-        NewWindow,
-        CloseWindow,
-        CloseInactiveTabsAndPanes,
-        AddFolderToProject,
-        Unfollow,
-        SaveAs,
-        ReloadActiveItem,
-        ActivatePreviousPane,
-        ActivateNextPane,
-        FollowNextCollaborator,
-        NewTerminal,
-        NewCenterTerminal,
-        ToggleTerminalFocus,
-        NewSearch,
-        Feedback,
-        Restart,
-        Welcome,
-        ToggleZoom,
-        ToggleLeftDock,
-        ToggleRightDock,
-        ToggleBottomDock,
-        CloseAllDocks,
-    ]
-);
-
-#[derive(Clone, PartialEq)]
-pub struct OpenPaths {
-    pub paths: Vec<PathBuf>,
-}
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct ActivatePane(pub usize);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct ActivatePaneInDirection(pub SplitDirection);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct SwapPaneInDirection(pub SplitDirection);
-
-#[derive(Clone, Deserialize, PartialEq)]
-pub struct NewFileInDirection(pub SplitDirection);
-
-#[derive(Clone, PartialEq, Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct SaveAll {
-    pub save_intent: Option<SaveIntent>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Save {
-    pub save_intent: Option<SaveIntent>,
-}
-
-#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
-#[serde(rename_all = "camelCase")]
-pub struct CloseAllItemsAndPanes {
-    pub save_intent: Option<SaveIntent>,
-}
-
-impl_actions!(
-    workspace,
-    [
-        ActivatePane,
-        ActivatePaneInDirection,
-        CloseAllItemsAndPanes,
-        NewFileInDirection,
-        OpenTerminal,
-        Save,
-        SaveAll,
-        SwapPaneInDirection,
-    ]
-);
-
-#[derive(Deserialize)]
-pub struct Toast {
-    id: usize,
-    msg: Cow<'static, str>,
-    #[serde(skip)]
-    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
-}
-
-impl Toast {
-    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
-        Toast {
-            id,
-            msg: msg.into(),
-            on_click: None,
-        }
-    }
-
-    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
-    where
-        M: Into<Cow<'static, str>>,
-        F: Fn(&mut WindowContext) + 'static,
-    {
-        self.on_click = Some((message.into(), Arc::new(on_click)));
-        self
-    }
-}
-
-impl PartialEq for Toast {
-    fn eq(&self, other: &Self) -> bool {
-        self.id == other.id
-            && self.msg == other.msg
-            && self.on_click.is_some() == other.on_click.is_some()
-    }
-}
-
-impl Clone for Toast {
-    fn clone(&self) -> Self {
-        Toast {
-            id: self.id,
-            msg: self.msg.to_owned(),
-            on_click: self.on_click.clone(),
-        }
-    }
-}
-
-#[derive(Debug, Default, Clone, Deserialize, PartialEq)]
-pub struct OpenTerminal {
-    pub working_directory: PathBuf,
-}
-
-pub type WorkspaceId = i64;
-
-pub fn init_settings(cx: &mut AppContext) {
-    WorkspaceSettings::register(cx);
-    ItemSettings::register(cx);
-}
-
-pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-    init_settings(cx);
-    notifications::init(cx);
-
-    cx.on_action(Workspace::close_global);
-    cx.on_action(restart);
-
-    cx.on_action({
-        let app_state = Arc::downgrade(&app_state);
-        move |_: &Open, cx: &mut AppContext| {
-            let paths = cx.prompt_for_paths(PathPromptOptions {
-                files: true,
-                directories: true,
-                multiple: true,
-            });
-
-            if let Some(app_state) = app_state.upgrade() {
-                cx.spawn(move |cx| async move {
-                    if let Some(paths) = paths.await.log_err().flatten() {
-                        cx.update(|cx| {
-                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
-                        })
-                        .ok();
-                    }
-                })
-                .detach();
-            }
-        }
-    });
-}
-
-type ProjectItemBuilders =
-    HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
-pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
-    let builders = cx.default_global::<ProjectItemBuilders>();
-    builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
-        let item = model.downcast::<I::Item>().unwrap();
-        Box::new(cx.new_view(|cx| I::for_project_item(project, item, cx)))
-    });
-}
-
-type FollowableItemBuilder = fn(
-    View<Pane>,
-    View<Workspace>,
-    ViewId,
-    &mut Option<proto::view::Variant>,
-    &mut WindowContext,
-) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
-type FollowableItemBuilders = HashMap<
-    TypeId,
-    (
-        FollowableItemBuilder,
-        fn(&AnyView) -> Box<dyn FollowableItemHandle>,
-    ),
->;
-pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
-    let builders = cx.default_global::<FollowableItemBuilders>();
-    builders.insert(
-        TypeId::of::<I>(),
-        (
-            |pane, workspace, id, state, cx| {
-                I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
-                    cx.foreground_executor()
-                        .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
-                })
-            },
-            |this| Box::new(this.clone().downcast::<I>().unwrap()),
-        ),
-    );
-}
-
-type ItemDeserializers = HashMap<
-    Arc<str>,
-    fn(
-        Model<Project>,
-        WeakView<Workspace>,
-        WorkspaceId,
-        ItemId,
-        &mut ViewContext<Pane>,
-    ) -> Task<Result<Box<dyn ItemHandle>>>,
->;
-pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
-    if let Some(serialized_item_kind) = I::serialized_item_kind() {
-        let deserializers = cx.default_global::<ItemDeserializers>();
-        deserializers.insert(
-            Arc::from(serialized_item_kind),
-            |project, workspace, workspace_id, item_id, cx| {
-                let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
-                cx.foreground_executor()
-                    .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
-            },
-        );
-    }
-}
-
-pub struct AppState {
-    pub languages: Arc<LanguageRegistry>,
-    pub client: Arc<Client>,
-    pub user_store: Model<UserStore>,
-    pub workspace_store: Model<WorkspaceStore>,
-    pub fs: Arc<dyn fs::Fs>,
-    pub build_window_options:
-        fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
-    pub node_runtime: Arc<dyn NodeRuntime>,
-}
-
-pub struct WorkspaceStore {
-    workspaces: HashSet<WindowHandle<Workspace>>,
-    followers: Vec<Follower>,
-    client: Arc<Client>,
-    _subscriptions: Vec<client::Subscription>,
-}
-
-#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
-struct Follower {
-    project_id: Option<u64>,
-    peer_id: PeerId,
-}
-
-impl AppState {
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test(cx: &mut AppContext) -> Arc<Self> {
-        use node_runtime::FakeNodeRuntime;
-        use settings::SettingsStore;
-
-        if !cx.has_global::<SettingsStore>() {
-            let settings_store = SettingsStore::test(cx);
-            cx.set_global(settings_store);
-        }
-
-        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.new_model(|cx| UserStore::new(client.clone(), cx));
-        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
-
-        theme::init(theme::LoadThemes::JustBase, cx);
-        client::init(&client, cx);
-        crate::init_settings(cx);
-
-        Arc::new(Self {
-            client,
-            fs,
-            languages,
-            user_store,
-            workspace_store,
-            node_runtime: FakeNodeRuntime::new(),
-            build_window_options: |_, _, _| Default::default(),
-        })
-    }
-}
-
-struct DelayedDebouncedEditAction {
-    task: Option<Task<()>>,
-    cancel_channel: Option<oneshot::Sender<()>>,
-}
-
-impl DelayedDebouncedEditAction {
-    fn new() -> DelayedDebouncedEditAction {
-        DelayedDebouncedEditAction {
-            task: None,
-            cancel_channel: None,
-        }
-    }
-
-    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
-    where
-        F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
-    {
-        if let Some(channel) = self.cancel_channel.take() {
-            _ = channel.send(());
-        }
-
-        let (sender, mut receiver) = oneshot::channel::<()>();
-        self.cancel_channel = Some(sender);
-
-        let previous_task = self.task.take();
-        self.task = Some(cx.spawn(move |workspace, mut cx| async move {
-            let mut timer = cx.background_executor().timer(delay).fuse();
-            if let Some(previous_task) = previous_task {
-                previous_task.await;
-            }
-
-            futures::select_biased! {
-                _ = receiver => return,
-                    _ = timer => {}
-            }
-
-            if let Some(result) = workspace
-                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
-                .log_err()
-            {
-                result.await.log_err();
-            }
-        }));
-    }
-}
-
-pub enum Event {
-    PaneAdded(View<Pane>),
-    ContactRequestedJoin(u64),
-    WorkspaceCreated(WeakView<Workspace>),
-}
-
-pub struct Workspace {
-    weak_self: WeakView<Self>,
-    workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
-    zoomed: Option<AnyWeakView>,
-    zoomed_position: Option<DockPosition>,
-    center: PaneGroup,
-    left_dock: View<Dock>,
-    bottom_dock: View<Dock>,
-    right_dock: View<Dock>,
-    panes: Vec<View<Pane>>,
-    panes_by_item: HashMap<EntityId, WeakView<Pane>>,
-    active_pane: View<Pane>,
-    last_active_center_pane: Option<WeakView<Pane>>,
-    last_active_view_id: Option<proto::ViewId>,
-    status_bar: View<StatusBar>,
-    modal_layer: View<ModalLayer>,
-    titlebar_item: Option<AnyView>,
-    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
-    project: Model<Project>,
-    follower_states: HashMap<View<Pane>, FollowerState>,
-    last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
-    window_edited: bool,
-    active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
-    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
-    database_id: WorkspaceId,
-    app_state: Arc<AppState>,
-    _subscriptions: Vec<Subscription>,
-    _apply_leader_updates: Task<Result<()>>,
-    _observe_current_user: Task<Result<()>>,
-    _schedule_serialize: Option<Task<()>>,
-    pane_history_timestamp: Arc<AtomicUsize>,
-    bounds: Bounds<Pixels>,
-}
-
-impl EventEmitter<Event> for Workspace {}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-pub struct ViewId {
-    pub creator: PeerId,
-    pub id: u64,
-}
-
-#[derive(Default)]
-struct FollowerState {
-    leader_id: PeerId,
-    active_view_id: Option<ViewId>,
-    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
-}
-
-impl Workspace {
-    pub fn new(
-        workspace_id: WorkspaceId,
-        project: Model<Project>,
-        app_state: Arc<AppState>,
-        cx: &mut ViewContext<Self>,
-    ) -> Self {
-        cx.observe(&project, |_, _, cx| cx.notify()).detach();
-        cx.subscribe(&project, move |this, _, event, cx| {
-            match event {
-                project::Event::RemoteIdChanged(_) => {
-                    this.update_window_title(cx);
-                }
-
-                project::Event::CollaboratorLeft(peer_id) => {
-                    this.collaborator_left(*peer_id, cx);
-                }
-
-                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
-                    this.update_window_title(cx);
-                    this.serialize_workspace(cx);
-                }
-
-                project::Event::DisconnectedFromHost => {
-                    this.update_window_edited(cx);
-                    cx.disable_focus();
-                }
-
-                project::Event::Closed => {
-                    cx.remove_window();
-                }
-
-                project::Event::DeletedEntry(entry_id) => {
-                    for pane in this.panes.iter() {
-                        pane.update(cx, |pane, cx| {
-                            pane.handle_deleted_project_item(*entry_id, cx)
-                        });
-                    }
-                }
-
-                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
-                    cx.new_view(|_| MessageNotification::new(message.clone()))
-                }),
-
-                _ => {}
-            }
-            cx.notify()
-        })
-        .detach();
-
-        cx.on_blur_window(|this, cx| {
-            let focus_handle = this.focus_handle(cx);
-            cx.focus(&focus_handle);
-        })
-        .detach();
-
-        let weak_handle = cx.view().downgrade();
-        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
-
-        let center_pane = cx.new_view(|cx| {
-            Pane::new(
-                weak_handle.clone(),
-                project.clone(),
-                pane_history_timestamp.clone(),
-                None,
-                cx,
-            )
-        });
-        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
-
-        cx.focus_view(&center_pane);
-        cx.emit(Event::PaneAdded(center_pane.clone()));
-
-        let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
-        app_state.workspace_store.update(cx, |store, _| {
-            store.workspaces.insert(window_handle);
-        });
-
-        let mut current_user = app_state.user_store.read(cx).watch_current_user();
-        let mut connection_status = app_state.client.status();
-        let _observe_current_user = cx.spawn(|this, mut cx| async move {
-            current_user.next().await;
-            connection_status.next().await;
-            let mut stream =
-                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
-
-            while stream.recv().await.is_some() {
-                this.update(&mut cx, |_, cx| cx.notify())?;
-            }
-            anyhow::Ok(())
-        });
-
-        // All leader updates are enqueued and then processed in a single task, so
-        // that each asynchronous operation can be run in order.
-        let (leader_updates_tx, mut leader_updates_rx) =
-            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
-        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
-            while let Some((leader_id, update)) = leader_updates_rx.next().await {
-                Self::process_leader_update(&this, leader_id, update, &mut cx)
-                    .await
-                    .log_err();
-            }
-
-            Ok(())
-        });
-
-        cx.emit(Event::WorkspaceCreated(weak_handle.clone()));
-
-        let left_dock = Dock::new(DockPosition::Left, cx);
-        let bottom_dock = Dock::new(DockPosition::Bottom, cx);
-        let right_dock = Dock::new(DockPosition::Right, cx);
-        let left_dock_buttons = cx.new_view(|cx| PanelButtons::new(left_dock.clone(), cx));
-        let bottom_dock_buttons = cx.new_view(|cx| PanelButtons::new(bottom_dock.clone(), cx));
-        let right_dock_buttons = cx.new_view(|cx| PanelButtons::new(right_dock.clone(), cx));
-        let status_bar = cx.new_view(|cx| {
-            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
-            status_bar.add_left_item(left_dock_buttons, cx);
-            status_bar.add_right_item(right_dock_buttons, cx);
-            status_bar.add_right_item(bottom_dock_buttons, cx);
-            status_bar
-        });
-
-        let modal_layer = cx.new_view(|_| ModalLayer::new());
-
-        let mut active_call = None;
-        if cx.has_global::<Model<ActiveCall>>() {
-            let call = cx.global::<Model<ActiveCall>>().clone();
-            let mut subscriptions = Vec::new();
-            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
-            active_call = Some((call, subscriptions));
-        }
-
-        let subscriptions = vec![
-            cx.observe_window_activation(Self::on_window_activation_changed),
-            cx.observe_window_bounds(move |_, cx| {
-                if let Some(display) = cx.display() {
-                    // Transform fixed bounds to be stored in terms of the containing display
-                    let mut bounds = cx.window_bounds();
-                    if let WindowBounds::Fixed(window_bounds) = &mut bounds {
-                        let display_bounds = display.bounds();
-                        window_bounds.origin.x -= display_bounds.origin.x;
-                        window_bounds.origin.y -= display_bounds.origin.y;
-                    }
-
-                    if let Some(display_uuid) = display.uuid().log_err() {
-                        cx.background_executor()
-                            .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
-                            .detach_and_log_err(cx);
-                    }
-                }
-                cx.notify();
-            }),
-            cx.observe(&left_dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
-            cx.observe(&bottom_dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
-            cx.observe(&right_dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
-            cx.on_release(|this, window, cx| {
-                this.app_state.workspace_store.update(cx, |store, _| {
-                    let window = window.downcast::<Self>().unwrap();
-                    debug_assert!(store.workspaces.remove(&window));
-                })
-            }),
-        ];
-
-        cx.defer(|this, cx| {
-            this.update_window_title(cx);
-            // todo! @nate - these are useful for testing notifications
-            // this.show_error(
-            //     &anyhow::anyhow!("what happens if this message is very very very very very long"),
-            //     cx,
-            // );
-
-            // this.show_notification(1, cx, |cx| {
-            //     cx.build_view(|_cx| {
-            //         simple_message_notification::MessageNotification::new(format!("Error:"))
-            //             .with_click_message("click here because!")
-            //     })
-            // });
-        });
-        Workspace {
-            weak_self: weak_handle.clone(),
-            zoomed: None,
-            zoomed_position: None,
-            center: PaneGroup::new(center_pane.clone()),
-            panes: vec![center_pane.clone()],
-            panes_by_item: Default::default(),
-            active_pane: center_pane.clone(),
-            last_active_center_pane: Some(center_pane.downgrade()),
-            last_active_view_id: None,
-            status_bar,
-            modal_layer,
-            titlebar_item: None,
-            notifications: Default::default(),
-            left_dock,
-            bottom_dock,
-            right_dock,
-            project: project.clone(),
-            follower_states: Default::default(),
-            last_leaders_by_pane: Default::default(),
-            window_edited: false,
-            active_call,
-            database_id: workspace_id,
-            app_state,
-            _observe_current_user,
-            _apply_leader_updates,
-            _schedule_serialize: None,
-            leader_updates_tx,
-            _subscriptions: subscriptions,
-            pane_history_timestamp,
-            workspace_actions: Default::default(),
-            // This data will be incorrect, but it will be overwritten by the time it needs to be used.
-            bounds: Default::default(),
-        }
-    }
-
-    fn new_local(
-        abs_paths: Vec<PathBuf>,
-        app_state: Arc<AppState>,
-        requesting_window: Option<WindowHandle<Workspace>>,
-        cx: &mut AppContext,
-    ) -> Task<
-        anyhow::Result<(
-            WindowHandle<Workspace>,
-            Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-        )>,
-    > {
-        let project_handle = Project::local(
-            app_state.client.clone(),
-            app_state.node_runtime.clone(),
-            app_state.user_store.clone(),
-            app_state.languages.clone(),
-            app_state.fs.clone(),
-            cx,
-        );
-
-        cx.spawn(|mut cx| async move {
-            let serialized_workspace: Option<SerializedWorkspace> =
-                persistence::DB.workspace_for_roots(&abs_paths.as_slice());
-
-            let paths_to_open = Arc::new(abs_paths);
-
-            // Get project paths for all of the abs_paths
-            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
-            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
-                Vec::with_capacity(paths_to_open.len());
-            for path in paths_to_open.iter().cloned() {
-                if let Some((worktree, project_entry)) = cx
-                    .update(|cx| {
-                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
-                    })?
-                    .await
-                    .log_err()
-                {
-                    worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
-                    project_paths.push((path, Some(project_entry)));
-                } else {
-                    project_paths.push((path, None));
-                }
-            }
-
-            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
-                serialized_workspace.id
-            } else {
-                DB.next_id().await.unwrap_or(0)
-            };
-
-            let window = if let Some(window) = requesting_window {
-                cx.update_window(window.into(), |_, cx| {
-                    cx.replace_root_view(|cx| {
-                        Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
-                    });
-                })?;
-                window
-            } else {
-                let window_bounds_override = window_bounds_env_override(&cx);
-                let (bounds, display) = if let Some(bounds) = window_bounds_override {
-                    (Some(bounds), None)
-                } else {
-                    serialized_workspace
-                        .as_ref()
-                        .and_then(|serialized_workspace| {
-                            let serialized_display = serialized_workspace.display?;
-                            let mut bounds = serialized_workspace.bounds?;
-
-                            // Stored bounds are relative to the containing display.
-                            // So convert back to global coordinates if that screen still exists
-                            if let WindowBounds::Fixed(mut window_bounds) = bounds {
-                                let screen = cx
-                                    .update(|cx| {
-                                        cx.displays().into_iter().find(|display| {
-                                            display.uuid().ok() == Some(serialized_display)
-                                        })
-                                    })
-                                    .ok()??;
-                                let screen_bounds = screen.bounds();
-                                window_bounds.origin.x += screen_bounds.origin.x;
-                                window_bounds.origin.y += screen_bounds.origin.y;
-                                bounds = WindowBounds::Fixed(window_bounds);
-                            }
-
-                            Some((bounds, serialized_display))
-                        })
-                        .unzip()
-                };
-
-                // Use the serialized workspace to construct the new window
-                let options =
-                    cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
-
-                cx.open_window(options, {
-                    let app_state = app_state.clone();
-                    let workspace_id = workspace_id.clone();
-                    let project_handle = project_handle.clone();
-                    move |cx| {
-                        cx.new_view(|cx| {
-                            Workspace::new(workspace_id, project_handle, app_state, cx)
-                        })
-                    }
-                })?
-            };
-
-            window
-                .update(&mut cx, |_, cx| cx.activate_window())
-                .log_err();
-
-            notify_if_database_failed(window, &mut cx);
-            let opened_items = window
-                .update(&mut cx, |_workspace, cx| {
-                    open_items(serialized_workspace, project_paths, app_state, cx)
-                })?
-                .await
-                .unwrap_or_default();
-
-            Ok((window, opened_items))
-        })
-    }
-
-    pub fn weak_handle(&self) -> WeakView<Self> {
-        self.weak_self.clone()
-    }
-
-    pub fn left_dock(&self) -> &View<Dock> {
-        &self.left_dock
-    }
-
-    pub fn bottom_dock(&self) -> &View<Dock> {
-        &self.bottom_dock
-    }
-
-    pub fn right_dock(&self) -> &View<Dock> {
-        &self.right_dock
-    }
-
-    pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
-        let dock = match panel.position(cx) {
-            DockPosition::Left => &self.left_dock,
-            DockPosition::Bottom => &self.bottom_dock,
-            DockPosition::Right => &self.right_dock,
-        };
-
-        dock.update(cx, |dock, cx| {
-            dock.add_panel(panel, self.weak_self.clone(), cx)
-        });
-    }
-
-    pub fn status_bar(&self) -> &View<StatusBar> {
-        &self.status_bar
-    }
-
-    pub fn app_state(&self) -> &Arc<AppState> {
-        &self.app_state
-    }
-
-    pub fn user_store(&self) -> &Model<UserStore> {
-        &self.app_state.user_store
-    }
-
-    pub fn project(&self) -> &Model<Project> {
-        &self.project
-    }
-
-    pub fn recent_navigation_history(
-        &self,
-        limit: Option<usize>,
-        cx: &AppContext,
-    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
-        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
-        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
-        for pane in &self.panes {
-            let pane = pane.read(cx);
-            pane.nav_history()
-                .for_each_entry(cx, |entry, (project_path, fs_path)| {
-                    if let Some(fs_path) = &fs_path {
-                        abs_paths_opened
-                            .entry(fs_path.clone())
-                            .or_default()
-                            .insert(project_path.clone());
-                    }
-                    let timestamp = entry.timestamp;
-                    match history.entry(project_path) {
-                        hash_map::Entry::Occupied(mut entry) => {
-                            let (_, old_timestamp) = entry.get();
-                            if &timestamp > old_timestamp {
-                                entry.insert((fs_path, timestamp));
-                            }
-                        }
-                        hash_map::Entry::Vacant(entry) => {
-                            entry.insert((fs_path, timestamp));
-                        }
-                    }
-                });
-        }
-
-        history
-            .into_iter()
-            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
-            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
-            .rev()
-            .filter(|(history_path, abs_path)| {
-                let latest_project_path_opened = abs_path
-                    .as_ref()
-                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
-                    .and_then(|project_paths| {
-                        project_paths
-                            .iter()
-                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
-                    });
-
-                match latest_project_path_opened {
-                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
-                    None => true,
-                }
-            })
-            .take(limit.unwrap_or(usize::MAX))
-            .collect()
-    }
-
-    fn navigate_history(
-        &mut self,
-        pane: WeakView<Pane>,
-        mode: NavigationMode,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Task<Result<()>> {
-        let to_load = if let Some(pane) = pane.upgrade() {
-            // todo!("focus")
-            // cx.focus(&pane);
-
-            pane.update(cx, |pane, cx| {
-                loop {
-                    // Retrieve the weak item handle from the history.
-                    let entry = pane.nav_history_mut().pop(mode, cx)?;
-
-                    // If the item is still present in this pane, then activate it.
-                    if let Some(index) = entry
-                        .item
-                        .upgrade()
-                        .and_then(|v| pane.index_for_item(v.as_ref()))
-                    {
-                        let prev_active_item_index = pane.active_item_index();
-                        pane.nav_history_mut().set_mode(mode);
-                        pane.activate_item(index, true, true, cx);
-                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
-
-                        let mut navigated = prev_active_item_index != pane.active_item_index();
-                        if let Some(data) = entry.data {
-                            navigated |= pane.active_item()?.navigate(data, cx);
-                        }
-
-                        if navigated {
-                            break None;
-                        }
-                    }
-                    // If the item is no longer present in this pane, then retrieve its
-                    // project path in order to reopen it.
-                    else {
-                        break pane
-                            .nav_history()
-                            .path_for_item(entry.item.id())
-                            .map(|(project_path, _)| (project_path, entry));
-                    }
-                }
-            })
-        } else {
-            None
-        };
-
-        if let Some((project_path, entry)) = to_load {
-            // If the item was no longer present, then load it again from its previous path.
-            let task = self.load_path(project_path, cx);
-            cx.spawn(|workspace, mut cx| async move {
-                let task = task.await;
-                let mut navigated = false;
-                if let Some((project_entry_id, build_item)) = task.log_err() {
-                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
-                        pane.nav_history_mut().set_mode(mode);
-                        pane.active_item().map(|p| p.item_id())
-                    })?;
-
-                    pane.update(&mut cx, |pane, cx| {
-                        let item = pane.open_item(project_entry_id, true, cx, build_item);
-                        navigated |= Some(item.item_id()) != prev_active_item_id;
-                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
-                        if let Some(data) = entry.data {
-                            navigated |= item.navigate(data, cx);
-                        }
-                    })?;
-                }
-
-                if !navigated {
-                    workspace
-                        .update(&mut cx, |workspace, cx| {
-                            Self::navigate_history(workspace, pane, mode, cx)
-                        })?
-                        .await?;
-                }
-
-                Ok(())
-            })
-        } else {
-            Task::ready(Ok(()))
-        }
-    }
-
-    pub fn go_back(
-        &mut self,
-        pane: WeakView<Pane>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Task<Result<()>> {
-        self.navigate_history(pane, NavigationMode::GoingBack, cx)
-    }
-
-    pub fn go_forward(
-        &mut self,
-        pane: WeakView<Pane>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Task<Result<()>> {
-        self.navigate_history(pane, NavigationMode::GoingForward, cx)
-    }
-
-    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
-        self.navigate_history(
-            self.active_pane().downgrade(),
-            NavigationMode::ReopeningClosedItem,
-            cx,
-        )
-    }
-
-    pub fn client(&self) -> &Client {
-        &self.app_state.client
-    }
-
-    pub fn set_titlebar_item(&mut self, item: AnyView, cx: &mut ViewContext<Self>) {
-        self.titlebar_item = Some(item);
-        cx.notify();
-    }
-
-    pub fn titlebar_item(&self) -> Option<AnyView> {
-        self.titlebar_item.clone()
-    }
-
-    /// Call the given callback with a workspace whose project is local.
-    ///
-    /// If the given workspace has a local project, then it will be passed
-    /// to the callback. Otherwise, a new empty window will be created.
-    pub fn with_local_workspace<T, F>(
-        &mut self,
-        cx: &mut ViewContext<Self>,
-        callback: F,
-    ) -> Task<Result<T>>
-    where
-        T: 'static,
-        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
-    {
-        if self.project.read(cx).is_local() {
-            Task::Ready(Some(Ok(callback(self, cx))))
-        } else {
-            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
-            cx.spawn(|_vh, mut cx| async move {
-                let (workspace, _) = task.await?;
-                workspace.update(&mut cx, callback)
-            })
-        }
-    }
-
-    pub fn worktrees<'a>(&self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Model<Worktree>> {
-        self.project.read(cx).worktrees()
-    }
-
-    pub fn visible_worktrees<'a>(
-        &self,
-        cx: &'a AppContext,
-    ) -> impl 'a + Iterator<Item = Model<Worktree>> {
-        self.project.read(cx).visible_worktrees(cx)
-    }
-
-    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
-        let futures = self
-            .worktrees(cx)
-            .filter_map(|worktree| worktree.read(cx).as_local())
-            .map(|worktree| worktree.scan_complete())
-            .collect::<Vec<_>>();
-        async move {
-            for future in futures {
-                future.await;
-            }
-        }
-    }
-
-    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
-        cx.windows().iter().find(|window| {
-            window
-                .update(cx, |_, window| {
-                    if window.is_window_active() {
-                        //This can only get called when the window's project connection has been lost
-                        //so we don't need to prompt the user for anything and instead just close the window
-                        window.remove_window();
-                        true
-                    } else {
-                        false
-                    }
-                })
-                .unwrap_or(false)
-        });
-    }
-
-    pub fn close_window(&mut self, _: &CloseWindow, cx: &mut ViewContext<Self>) {
-        let window = cx.window_handle();
-        let prepare = self.prepare_to_close(false, cx);
-        cx.spawn(|_, mut cx| async move {
-            if prepare.await? {
-                window.update(&mut cx, |_, cx| {
-                    cx.remove_window();
-                })?;
-            }
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx)
-    }
-
-    pub fn prepare_to_close(
-        &mut self,
-        quitting: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<bool>> {
-        //todo!(saveing)
-        let active_call = self.active_call().cloned();
-        let window = cx.window_handle();
-
-        cx.spawn(|this, mut cx| async move {
-            let workspace_count = (*cx).update(|cx| {
-                cx.windows()
-                    .iter()
-                    .filter(|window| window.downcast::<Workspace>().is_some())
-                    .count()
-            })?;
-
-            if let Some(active_call) = active_call {
-                if !quitting
-                    && workspace_count == 1
-                    && active_call.read_with(&cx, |call, _| call.room().is_some())?
-                {
-                    let answer = window.update(&mut cx, |_, cx| {
-                        cx.prompt(
-                            PromptLevel::Warning,
-                            "Do you want to leave the current call?",
-                            &["Close window and hang up", "Cancel"],
-                        )
-                    })?;
-
-                    if answer.await.log_err() == Some(1) {
-                        return anyhow::Ok(false);
-                    } else {
-                        active_call
-                            .update(&mut cx, |call, cx| call.hang_up(cx))?
-                            .await
-                            .log_err();
-                    }
-                }
-            }
-
-            Ok(this
-                .update(&mut cx, |this, cx| {
-                    this.save_all_internal(SaveIntent::Close, cx)
-                })?
-                .await?)
-        })
-    }
-
-    fn save_all(&mut self, action: &SaveAll, cx: &mut ViewContext<Self>) {
-        self.save_all_internal(action.save_intent.unwrap_or(SaveIntent::SaveAll), cx)
-            .detach_and_log_err(cx);
-    }
-
-    fn save_all_internal(
-        &mut self,
-        mut save_intent: SaveIntent,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<bool>> {
-        if self.project.read(cx).is_read_only() {
-            return Task::ready(Ok(true));
-        }
-        let dirty_items = self
-            .panes
-            .iter()
-            .flat_map(|pane| {
-                pane.read(cx).items().filter_map(|item| {
-                    if item.is_dirty(cx) {
-                        Some((pane.downgrade(), item.boxed_clone()))
-                    } else {
-                        None
-                    }
-                })
-            })
-            .collect::<Vec<_>>();
-
-        let project = self.project.clone();
-        cx.spawn(|workspace, mut cx| async move {
-            // Override save mode and display "Save all files" prompt
-            if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
-                let answer = workspace.update(&mut cx, |_, cx| {
-                    let prompt = Pane::file_names_for_prompt(
-                        &mut dirty_items.iter().map(|(_, handle)| handle),
-                        dirty_items.len(),
-                        cx,
-                    );
-                    cx.prompt(
-                        PromptLevel::Warning,
-                        &prompt,
-                        &["Save all", "Discard all", "Cancel"],
-                    )
-                })?;
-                match answer.await.log_err() {
-                    Some(0) => save_intent = SaveIntent::SaveAll,
-                    Some(1) => save_intent = SaveIntent::Skip,
-                    _ => {}
-                }
-            }
-            for (pane, item) in dirty_items {
-                let (singleton, project_entry_ids) =
-                    cx.update(|_, cx| (item.is_singleton(cx), item.project_entry_ids(cx)))?;
-                if singleton || !project_entry_ids.is_empty() {
-                    if let Some(ix) =
-                        pane.update(&mut cx, |pane, _| pane.index_for_item(item.as_ref()))?
-                    {
-                        if !Pane::save_item(
-                            project.clone(),
-                            &pane,
-                            ix,
-                            &*item,
-                            save_intent,
-                            &mut cx,
-                        )
-                        .await?
-                        {
-                            return Ok(false);
-                        }
-                    }
-                }
-            }
-            Ok(true)
-        })
-    }
-
-    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) {
-        let telemetry_settings = TelemetrySettings::get_global(cx).clone();
-        self.client()
-            .telemetry()
-            .report_app_event(telemetry_settings, "open project", false);
-        let paths = cx.prompt_for_paths(PathPromptOptions {
-            files: true,
-            directories: true,
-            multiple: true,
-        });
-
-        cx.spawn(|this, mut cx| async move {
-            let Some(paths) = paths.await.log_err().flatten() else {
-                return;
-            };
-
-            if let Some(task) = this
-                .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
-                .log_err()
-            {
-                task.await.log_err();
-            }
-        })
-        .detach()
-    }
-
-    pub fn open_workspace_for_paths(
-        &mut self,
-        paths: Vec<PathBuf>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let window = cx.window_handle().downcast::<Self>();
-        let is_remote = self.project.read(cx).is_remote();
-        let has_worktree = self.project.read(cx).worktrees().next().is_some();
-        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
-        let close_task = if is_remote || has_worktree || has_dirty_items {
-            None
-        } else {
-            Some(self.prepare_to_close(false, cx))
-        };
-        let app_state = self.app_state.clone();
-
-        cx.spawn(|_, mut cx| async move {
-            let window_to_replace = if let Some(close_task) = close_task {
-                if !close_task.await? {
-                    return Ok(());
-                }
-                window
-            } else {
-                None
-            };
-            cx.update(|_, cx| open_paths(&paths, &app_state, window_to_replace, cx))?
-                .await?;
-            Ok(())
-        })
-    }
-
-    #[allow(clippy::type_complexity)]
-    pub fn open_paths(
-        &mut self,
-        mut abs_paths: Vec<PathBuf>,
-        visible: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
-        log::info!("open paths {abs_paths:?}");
-
-        let fs = self.app_state.fs.clone();
-
-        // Sort the paths to ensure we add worktrees for parents before their children.
-        abs_paths.sort_unstable();
-        cx.spawn(move |this, mut cx| async move {
-            let mut tasks = Vec::with_capacity(abs_paths.len());
-            for abs_path in &abs_paths {
-                let project_path = match this
-                    .update(&mut cx, |this, cx| {
-                        Workspace::project_path_for_path(
-                            this.project.clone(),
-                            abs_path,
-                            visible,
-                            cx,
-                        )
-                    })
-                    .log_err()
-                {
-                    Some(project_path) => project_path.await.log_err(),
-                    None => None,
-                };
-
-                let this = this.clone();
-                let abs_path = abs_path.clone();
-                let fs = fs.clone();
-                let task = cx.spawn(move |mut cx| async move {
-                    let (worktree, project_path) = project_path?;
-                    if fs.is_file(&abs_path).await {
-                        Some(
-                            this.update(&mut cx, |this, cx| {
-                                this.open_path(project_path, None, true, cx)
-                            })
-                            .log_err()?
-                            .await,
-                        )
-                    } else {
-                        this.update(&mut cx, |workspace, cx| {
-                            let worktree = worktree.read(cx);
-                            let worktree_abs_path = worktree.abs_path();
-                            let entry_id = if abs_path == worktree_abs_path.as_ref() {
-                                worktree.root_entry()
-                            } else {
-                                abs_path
-                                    .strip_prefix(worktree_abs_path.as_ref())
-                                    .ok()
-                                    .and_then(|relative_path| {
-                                        worktree.entry_for_path(relative_path)
-                                    })
-                            }
-                            .map(|entry| entry.id);
-                            if let Some(entry_id) = entry_id {
-                                workspace.project.update(cx, |_, cx| {
-                                    cx.emit(project::Event::ActiveEntryChanged(Some(entry_id)));
-                                })
-                            }
-                        })
-                        .log_err()?;
-                        None
-                    }
-                });
-                tasks.push(task);
-            }
-
-            futures::future::join_all(tasks).await
-        })
-    }
-
-    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
-        let paths = cx.prompt_for_paths(PathPromptOptions {
-            files: false,
-            directories: true,
-            multiple: true,
-        });
-        cx.spawn(|this, mut cx| async move {
-            if let Some(paths) = paths.await.log_err().flatten() {
-                let results = this
-                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
-                    .await;
-                for result in results.into_iter().flatten() {
-                    result.log_err();
-                }
-            }
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-    }
-
-    fn project_path_for_path(
-        project: Model<Project>,
-        abs_path: &Path,
-        visible: bool,
-        cx: &mut AppContext,
-    ) -> Task<Result<(Model<Worktree>, ProjectPath)>> {
-        let entry = project.update(cx, |project, cx| {
-            project.find_or_create_local_worktree(abs_path, visible, cx)
-        });
-        cx.spawn(|mut cx| async move {
-            let (worktree, path) = entry.await?;
-            let worktree_id = worktree.update(&mut cx, |t, _| t.id())?;
-            Ok((
-                worktree,
-                ProjectPath {
-                    worktree_id,
-                    path: path.into(),
-                },
-            ))
-        })
-    }
-
-    pub fn items<'a>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
-        self.panes.iter().flat_map(|pane| pane.read(cx).items())
-    }
-
-    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<View<T>> {
-        self.items_of_type(cx).max_by_key(|item| item.item_id())
-    }
-
-    pub fn items_of_type<'a, T: Item>(
-        &'a self,
-        cx: &'a AppContext,
-    ) -> impl 'a + Iterator<Item = View<T>> {
-        self.panes
-            .iter()
-            .flat_map(|pane| pane.read(cx).items_of_type())
-    }
-
-    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-        self.active_pane().read(cx).active_item()
-    }
-
-    pub fn active_item_as<I: 'static>(&self, cx: &AppContext) -> Option<View<I>> {
-        let item = self.active_item(cx)?;
-        item.to_any().downcast::<I>().ok()
-    }
-
-    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
-        self.active_item(cx).and_then(|item| item.project_path(cx))
-    }
-
-    pub fn save_active_item(
-        &mut self,
-        save_intent: SaveIntent,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<()>> {
-        let project = self.project.clone();
-        let pane = self.active_pane();
-        let item_ix = pane.read(cx).active_item_index();
-        let item = pane.read(cx).active_item();
-        let pane = pane.downgrade();
-
-        cx.spawn(|_, mut cx| async move {
-            if let Some(item) = item {
-                Pane::save_item(project, &pane, item_ix, item.as_ref(), save_intent, &mut cx)
-                    .await
-                    .map(|_| ())
-            } else {
-                Ok(())
-            }
-        })
-    }
-
-    pub fn close_inactive_items_and_panes(
-        &mut self,
-        _: &CloseInactiveTabsAndPanes,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.close_all_internal(true, SaveIntent::Close, cx)
-            .map(|task| task.detach_and_log_err(cx));
-    }
-
-    pub fn close_all_items_and_panes(
-        &mut self,
-        action: &CloseAllItemsAndPanes,
-        cx: &mut ViewContext<Self>,
-    ) {
-        self.close_all_internal(false, action.save_intent.unwrap_or(SaveIntent::Close), cx)
-            .map(|task| task.detach_and_log_err(cx));
-    }
-
-    fn close_all_internal(
-        &mut self,
-        retain_active_pane: bool,
-        save_intent: SaveIntent,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let current_pane = self.active_pane();
-
-        let mut tasks = Vec::new();
-
-        if retain_active_pane {
-            if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
-                pane.close_inactive_items(&CloseInactiveItems, cx)
-            }) {
-                tasks.push(current_pane_close);
-            };
-        }
-
-        for pane in self.panes() {
-            if retain_active_pane && pane.entity_id() == current_pane.entity_id() {
-                continue;
-            }
-
-            if let Some(close_pane_items) = pane.update(cx, |pane: &mut Pane, cx| {
-                pane.close_all_items(
-                    &CloseAllItems {
-                        save_intent: Some(save_intent),
-                    },
-                    cx,
-                )
-            }) {
-                tasks.push(close_pane_items)
-            }
-        }
-
-        if tasks.is_empty() {
-            None
-        } else {
-            Some(cx.spawn(|_, _| async move {
-                for task in tasks {
-                    task.await?
-                }
-                Ok(())
-            }))
-        }
-    }
-
-    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
-        let dock = match dock_side {
-            DockPosition::Left => &self.left_dock,
-            DockPosition::Bottom => &self.bottom_dock,
-            DockPosition::Right => &self.right_dock,
-        };
-        let mut focus_center = false;
-        let mut reveal_dock = false;
-        dock.update(cx, |dock, cx| {
-            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
-            let was_visible = dock.is_open() && !other_is_zoomed;
-            dock.set_open(!was_visible, cx);
-
-            if let Some(active_panel) = dock.active_panel() {
-                if was_visible {
-                    if active_panel.focus_handle(cx).contains_focused(cx) {
-                        focus_center = true;
-                    }
-                } else {
-                    let focus_handle = &active_panel.focus_handle(cx);
-                    cx.focus(focus_handle);
-                    reveal_dock = true;
-                }
-            }
-        });
-
-        if reveal_dock {
-            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
-        }
-
-        if focus_center {
-            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
-        }
-
-        cx.notify();
-        self.serialize_workspace(cx);
-    }
-
-    pub fn close_all_docks(&mut self, cx: &mut ViewContext<Self>) {
-        let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock];
-
-        for dock in docks {
-            dock.update(cx, |dock, cx| {
-                dock.set_open(false, cx);
-            });
-        }
-
-        // todo!("focus")
-        // cx.focus_self();
-        cx.notify();
-        self.serialize_workspace(cx);
-    }
-
-    /// Transfer focus to the panel of the given type.
-    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<View<T>> {
-        let panel = self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?;
-        panel.to_any().downcast().ok()
-    }
-
-    /// Focus the panel of the given type if it isn't already focused. If it is
-    /// already focused, then transfer focus back to the workspace center.
-    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
-        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| {
-            !panel.focus_handle(cx).contains_focused(cx)
-        });
-    }
-
-    /// Focus or unfocus the given panel type, depending on the given callback.
-    fn focus_or_unfocus_panel<T: Panel>(
-        &mut self,
-        cx: &mut ViewContext<Self>,
-        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
-    ) -> Option<Arc<dyn PanelHandle>> {
-        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
-            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
-                let mut focus_center = false;
-                let panel = dock.update(cx, |dock, cx| {
-                    dock.activate_panel(panel_index, cx);
-
-                    let panel = dock.active_panel().cloned();
-                    if let Some(panel) = panel.as_ref() {
-                        if should_focus(&**panel, cx) {
-                            dock.set_open(true, cx);
-                            panel.focus_handle(cx).focus(cx);
-                        } else {
-                            focus_center = true;
-                        }
-                    }
-                    panel
-                });
-
-                if focus_center {
-                    self.active_pane.update(cx, |pane, cx| pane.focus(cx))
-                }
-
-                self.serialize_workspace(cx);
-                cx.notify();
-                return panel;
-            }
-        }
-        None
-    }
-
-    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<View<T>> {
-        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
-            let dock = dock.read(cx);
-            if let Some(panel) = dock.panel::<T>() {
-                return Some(panel);
-            }
-        }
-        None
-    }
-
-    // todo!("implement zoom")
-    #[allow(unused)]
-    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
-        for pane in &self.panes {
-            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-        }
-
-        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
-        self.zoomed = None;
-        self.zoomed_position = None;
-
-        cx.notify();
-    }
-
-    //     #[cfg(any(test, feature = "test-support"))]
-    //     pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
-    //         self.zoomed.and_then(|view| view.upgrade(cx))
-    //     }
-
-    fn dismiss_zoomed_items_to_reveal(
-        &mut self,
-        dock_to_reveal: Option<DockPosition>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        // If a center pane is zoomed, unzoom it.
-        for pane in &self.panes {
-            if pane != &self.active_pane || dock_to_reveal.is_some() {
-                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-            }
-        }
-
-        // If another dock is zoomed, hide it.
-        let mut focus_center = false;
-        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
-            dock.update(cx, |dock, cx| {
-                if Some(dock.position()) != dock_to_reveal {
-                    if let Some(panel) = dock.active_panel() {
-                        if panel.is_zoomed(cx) {
-                            focus_center |= panel.focus_handle(cx).contains_focused(cx);
-                            dock.set_open(false, cx);
-                        }
-                    }
-                }
-            });
-        }
-
-        if focus_center {
-            self.active_pane.update(cx, |pane, cx| pane.focus(cx))
-        }
-
-        if self.zoomed_position != dock_to_reveal {
-            self.zoomed = None;
-            self.zoomed_position = None;
-        }
-
-        cx.notify();
-    }
-
-    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> View<Pane> {
-        let pane = cx.new_view(|cx| {
-            Pane::new(
-                self.weak_handle(),
-                self.project.clone(),
-                self.pane_history_timestamp.clone(),
-                None,
-                cx,
-            )
-        });
-        cx.subscribe(&pane, Self::handle_pane_event).detach();
-        self.panes.push(pane.clone());
-        cx.focus_view(&pane);
-        cx.emit(Event::PaneAdded(pane.clone()));
-        pane
-    }
-
-    pub fn add_item_to_center(
-        &mut self,
-        item: Box<dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) -> bool {
-        if let Some(center_pane) = self.last_active_center_pane.clone() {
-            if let Some(center_pane) = center_pane.upgrade() {
-                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
-                true
-            } else {
-                false
-            }
-        } else {
-            false
-        }
-    }
-
-    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-        self.active_pane
-            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
-    }
-
-    pub fn split_item(
-        &mut self,
-        split_direction: SplitDirection,
-        item: Box<dyn ItemHandle>,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let new_pane = self.split_pane(self.active_pane.clone(), split_direction, cx);
-        new_pane.update(cx, move |new_pane, cx| {
-            new_pane.add_item(item, true, true, None, cx)
-        })
-    }
-
-    pub fn open_abs_path(
-        &mut self,
-        abs_path: PathBuf,
-        visible: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
-        cx.spawn(|workspace, mut cx| async move {
-            let open_paths_task_result = workspace
-                .update(&mut cx, |workspace, cx| {
-                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
-                })
-                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
-                .await;
-            anyhow::ensure!(
-                open_paths_task_result.len() == 1,
-                "open abs path {abs_path:?} task returned incorrect number of results"
-            );
-            match open_paths_task_result
-                .into_iter()
-                .next()
-                .expect("ensured single task result")
-            {
-                Some(open_result) => {
-                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
-                }
-                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
-            }
-        })
-    }
-
-    pub fn split_abs_path(
-        &mut self,
-        abs_path: PathBuf,
-        visible: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
-        let project_path_task =
-            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
-        cx.spawn(|this, mut cx| async move {
-            let (_, path) = project_path_task.await?;
-            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
-                .await
-        })
-    }
-
-    pub fn open_path(
-        &mut self,
-        path: impl Into<ProjectPath>,
-        pane: Option<WeakView<Pane>>,
-        focus_item: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
-        let pane = pane.unwrap_or_else(|| {
-            self.last_active_center_pane.clone().unwrap_or_else(|| {
-                self.panes
-                    .first()
-                    .expect("There must be an active pane")
-                    .downgrade()
-            })
-        });
-
-        let task = self.load_path(path.into(), cx);
-        cx.spawn(move |_, mut cx| async move {
-            let (project_entry_id, build_item) = task.await?;
-            pane.update(&mut cx, |pane, cx| {
-                pane.open_item(project_entry_id, focus_item, cx, build_item)
-            })
-        })
-    }
-
-    pub fn split_path(
-        &mut self,
-        path: impl Into<ProjectPath>,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
-        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
-            self.panes
-                .first()
-                .expect("There must be an active pane")
-                .downgrade()
-        });
-
-        if let Member::Pane(center_pane) = &self.center.root {
-            if center_pane.read(cx).items_len() == 0 {
-                return self.open_path(path, Some(pane), true, cx);
-            }
-        }
-
-        let task = self.load_path(path.into(), cx);
-        cx.spawn(|this, mut cx| async move {
-            let (project_entry_id, build_item) = task.await?;
-            this.update(&mut cx, move |this, cx| -> Option<_> {
-                let pane = pane.upgrade()?;
-                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
-                new_pane.update(cx, |new_pane, cx| {
-                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
-                })
-            })
-            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
-        })
-    }
-
-    fn load_path(
-        &mut self,
-        path: ProjectPath,
-        cx: &mut ViewContext<Self>,
-    ) -> Task<
-        Result<(
-            Option<ProjectEntryId>,
-            impl 'static + Send + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
-        )>,
-    > {
-        let project = self.project().clone();
-        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
-        cx.spawn(|_, mut cx| async move {
-            let (project_entry_id, project_item) = project_item.await?;
-            let build_item = cx.update(|_, cx| {
-                cx.default_global::<ProjectItemBuilders>()
-                    .get(&project_item.entity_type())
-                    .ok_or_else(|| anyhow!("no item builder for project item"))
-                    .cloned()
-            })??;
-            let build_item =
-                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
-            Ok((project_entry_id, build_item))
-        })
-    }
-
-    pub fn open_project_item<T>(
-        &mut self,
-        project_item: Model<T::Item>,
-        cx: &mut ViewContext<Self>,
-    ) -> View<T>
-    where
-        T: ProjectItem,
-    {
-        use project::Item as _;
-
-        let entry_id = project_item.read(cx).entry_id(cx);
-        if let Some(item) = entry_id
-            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
-            .and_then(|item| item.downcast())
-        {
-            self.activate_item(&item, cx);
-            return item;
-        }
-
-        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
-        self.add_item(Box::new(item.clone()), cx);
-        item
-    }
-
-    pub fn split_project_item<T>(
-        &mut self,
-        project_item: Model<T::Item>,
-        cx: &mut ViewContext<Self>,
-    ) -> View<T>
-    where
-        T: ProjectItem,
-    {
-        use project::Item as _;
-
-        let entry_id = project_item.read(cx).entry_id(cx);
-        if let Some(item) = entry_id
-            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
-            .and_then(|item| item.downcast())
-        {
-            self.activate_item(&item, cx);
-            return item;
-        }
-
-        let item = cx.new_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
-        self.split_item(SplitDirection::Right, Box::new(item.clone()), cx);
-        item
-    }
-
-    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
-            self.active_pane.update(cx, |pane, cx| {
-                pane.add_item(Box::new(shared_screen), false, true, None, cx)
-            });
-        }
-    }
-
-    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
-        let result = self.panes.iter().find_map(|pane| {
-            pane.read(cx)
-                .index_for_item(item)
-                .map(|ix| (pane.clone(), ix))
-        });
-        if let Some((pane, ix)) = result {
-            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
-            true
-        } else {
-            false
-        }
-    }
-
-    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
-        let panes = self.center.panes();
-        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
-            cx.focus_view(&pane);
-        } else {
-            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
-        }
-    }
-
-    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
-        let panes = self.center.panes();
-        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
-            let next_ix = (ix + 1) % panes.len();
-            let next_pane = panes[next_ix].clone();
-            cx.focus_view(&next_pane);
-        }
-    }
-
-    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
-        let panes = self.center.panes();
-        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
-            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
-            let prev_pane = panes[prev_ix].clone();
-            cx.focus_view(&prev_pane);
-        }
-    }
-
-    pub fn activate_pane_in_direction(
-        &mut self,
-        direction: SplitDirection,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(pane) = self.find_pane_in_direction(direction, cx) {
-            cx.focus_view(pane);
-        }
-    }
-
-    pub fn swap_pane_in_direction(
-        &mut self,
-        direction: SplitDirection,
-        cx: &mut ViewContext<Self>,
-    ) {
-        if let Some(to) = self
-            .find_pane_in_direction(direction, cx)
-            .map(|pane| pane.clone())
-        {
-            self.center.swap(&self.active_pane.clone(), &to);
-            cx.notify();
-        }
-    }
-
-    fn find_pane_in_direction(
-        &mut self,
-        direction: SplitDirection,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<&View<Pane>> {
-        let Some(bounding_box) = self.center.bounding_box_for_pane(&self.active_pane) else {
-            return None;
-        };
-        let cursor = self.active_pane.read(cx).pixel_position_of_cursor(cx);
-        let center = match cursor {
-            Some(cursor) if bounding_box.contains(&cursor) => cursor,
-            _ => bounding_box.center(),
-        };
-
-        let distance_to_next = 8.; //todo(pane dividers styling)
-
-        let target = match direction {
-            SplitDirection::Left => {
-                Point::new(bounding_box.left() - distance_to_next.into(), center.y)
-            }
-            SplitDirection::Right => {
-                Point::new(bounding_box.right() + distance_to_next.into(), center.y)
-            }
-            SplitDirection::Up => {
-                Point::new(center.x, bounding_box.top() - distance_to_next.into())
-            }
-            SplitDirection::Down => {
-                Point::new(center.x, bounding_box.bottom() + distance_to_next.into())
-            }
-        };
-        self.center.pane_at_pixel_position(target)
-    }
-
-    fn handle_pane_focused(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
-        if self.active_pane != pane {
-            self.active_pane = pane.clone();
-            self.status_bar.update(cx, |status_bar, cx| {
-                status_bar.set_active_pane(&self.active_pane, cx);
-            });
-            self.active_item_path_changed(cx);
-            self.last_active_center_pane = Some(pane.downgrade());
-        }
-
-        self.dismiss_zoomed_items_to_reveal(None, cx);
-        if pane.read(cx).is_zoomed() {
-            self.zoomed = Some(pane.downgrade().into());
-        } else {
-            self.zoomed = None;
-        }
-        self.zoomed_position = None;
-        self.update_active_view_for_followers(cx);
-
-        cx.notify();
-    }
-
-    fn handle_pane_event(
-        &mut self,
-        pane: View<Pane>,
-        event: &pane::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
-            pane::Event::Split(direction) => {
-                self.split_and_clone(pane, *direction, cx);
-            }
-            pane::Event::Remove => self.remove_pane(pane, cx),
-            pane::Event::ActivateItem { local } => {
-                if *local {
-                    self.unfollow(&pane, cx);
-                }
-                if &pane == self.active_pane() {
-                    self.active_item_path_changed(cx);
-                    self.update_active_view_for_followers(cx);
-                }
-            }
-            pane::Event::ChangeItemTitle => {
-                if pane == self.active_pane {
-                    self.active_item_path_changed(cx);
-                }
-                self.update_window_edited(cx);
-            }
-            pane::Event::RemoveItem { item_id } => {
-                self.update_window_edited(cx);
-                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
-                    if entry.get().entity_id() == pane.entity_id() {
-                        entry.remove();
-                    }
-                }
-            }
-            pane::Event::Focus => {
-                self.handle_pane_focused(pane.clone(), cx);
-            }
-            pane::Event::ZoomIn => {
-                if pane == self.active_pane {
-                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
-                    if pane.read(cx).has_focus(cx) {
-                        self.zoomed = Some(pane.downgrade().into());
-                        self.zoomed_position = None;
-                    }
-                    cx.notify();
-                }
-            }
-            pane::Event::ZoomOut => {
-                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
-                if self.zoomed_position.is_none() {
-                    self.zoomed = None;
-                }
-                cx.notify();
-            }
-        }
-
-        self.serialize_workspace(cx);
-    }
-
-    pub fn split_pane(
-        &mut self,
-        pane_to_split: View<Pane>,
-        split_direction: SplitDirection,
-        cx: &mut ViewContext<Self>,
-    ) -> View<Pane> {
-        let new_pane = self.add_pane(cx);
-        self.center
-            .split(&pane_to_split, &new_pane, split_direction)
-            .unwrap();
-        cx.notify();
-        new_pane
-    }
-
-    pub fn split_and_clone(
-        &mut self,
-        pane: View<Pane>,
-        direction: SplitDirection,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<Pane>> {
-        let item = pane.read(cx).active_item()?;
-        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
-            let new_pane = self.add_pane(cx);
-            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
-            self.center.split(&pane, &new_pane, direction).unwrap();
-            Some(new_pane)
-        } else {
-            None
-        };
-        cx.notify();
-        maybe_pane_handle
-    }
-
-    pub fn split_pane_with_item(
-        &mut self,
-        pane_to_split: WeakView<Pane>,
-        split_direction: SplitDirection,
-        from: WeakView<Pane>,
-        item_id_to_move: EntityId,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let Some(pane_to_split) = pane_to_split.upgrade() else {
-            return;
-        };
-        let Some(from) = from.upgrade() else {
-            return;
-        };
-
-        let new_pane = self.add_pane(cx);
-        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
-        self.center
-            .split(&pane_to_split, &new_pane, split_direction)
-            .unwrap();
-        cx.notify();
-    }
-
-    pub fn split_pane_with_project_entry(
-        &mut self,
-        pane_to_split: WeakView<Pane>,
-        split_direction: SplitDirection,
-        project_entry: ProjectEntryId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let pane_to_split = pane_to_split.upgrade()?;
-        let new_pane = self.add_pane(cx);
-        self.center
-            .split(&pane_to_split, &new_pane, split_direction)
-            .unwrap();
-
-        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
-        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
-        Some(cx.foreground_executor().spawn(async move {
-            task.await?;
-            Ok(())
-        }))
-    }
-
-    pub fn move_item(
-        &mut self,
-        source: View<Pane>,
-        destination: View<Pane>,
-        item_id_to_move: EntityId,
-        destination_index: usize,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let item_to_move = source
-            .read(cx)
-            .items()
-            .enumerate()
-            .find(|(_, item_handle)| item_handle.item_id() == item_id_to_move);
-
-        if item_to_move.is_none() {
-            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
-            return;
-        }
-        let (item_ix, item_handle) = item_to_move.unwrap();
-        let item_handle = item_handle.clone();
-
-        if source != destination {
-            // Close item from previous pane
-            source.update(cx, |source, cx| {
-                source.remove_item(item_ix, false, cx);
-            });
-        }
-
-        // This automatically removes duplicate items in the pane
-        destination.update(cx, |destination, cx| {
-            destination.add_item(item_handle, true, true, Some(destination_index), cx);
-            destination.focus(cx)
-        });
-    }
-
-    fn remove_pane(&mut self, pane: View<Pane>, cx: &mut ViewContext<Self>) {
-        if self.center.remove(&pane).unwrap() {
-            self.force_remove_pane(&pane, cx);
-            self.unfollow(&pane, cx);
-            self.last_leaders_by_pane.remove(&pane.downgrade());
-            for removed_item in pane.read(cx).items() {
-                self.panes_by_item.remove(&removed_item.item_id());
-            }
-
-            cx.notify();
-        } else {
-            self.active_item_path_changed(cx);
-        }
-    }
-
-    pub fn panes(&self) -> &[View<Pane>] {
-        &self.panes
-    }
-
-    pub fn active_pane(&self) -> &View<Pane> {
-        &self.active_pane
-    }
-
-    pub fn pane_for(&self, handle: &dyn ItemHandle) -> Option<View<Pane>> {
-        let weak_pane = self.panes_by_item.get(&handle.item_id())?;
-        weak_pane.upgrade()
-    }
-
-    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
-        self.follower_states.retain(|_, state| {
-            if state.leader_id == peer_id {
-                for item in state.items_by_leader_view_id.values() {
-                    item.set_leader_peer_id(None, cx);
-                }
-                false
-            } else {
-                true
-            }
-        });
-        cx.notify();
-    }
-
-    pub fn start_following(
-        &mut self,
-        leader_id: PeerId,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<Task<Result<()>>> {
-        let pane = self.active_pane().clone();
-
-        self.last_leaders_by_pane
-            .insert(pane.downgrade(), leader_id);
-        self.unfollow(&pane, cx);
-        self.follower_states.insert(
-            pane.clone(),
-            FollowerState {
-                leader_id,
-                active_view_id: None,
-                items_by_leader_view_id: Default::default(),
-            },
-        );
-        cx.notify();
-
-        let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-        let project_id = self.project.read(cx).remote_id();
-        let request = self.app_state.client.request(proto::Follow {
-            room_id,
-            project_id,
-            leader_id: Some(leader_id),
-        });
-
-        Some(cx.spawn(|this, mut cx| async move {
-            let response = request.await?;
-            this.update(&mut cx, |this, _| {
-                let state = this
-                    .follower_states
-                    .get_mut(&pane)
-                    .ok_or_else(|| anyhow!("following interrupted"))?;
-                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
-                    Some(ViewId::from_proto(active_view_id)?)
-                } else {
-                    None
-                };
-                Ok::<_, anyhow::Error>(())
-            })??;
-            Self::add_views_from_leader(
-                this.clone(),
-                leader_id,
-                vec![pane],
-                response.views,
-                &mut cx,
-            )
-            .await?;
-            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
-            Ok(())
-        }))
-    }
-
-    pub fn follow_next_collaborator(
-        &mut self,
-        _: &FollowNextCollaborator,
-        cx: &mut ViewContext<Self>,
-    ) {
-        let collaborators = self.project.read(cx).collaborators();
-        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
-            let mut collaborators = collaborators.keys().copied();
-            for peer_id in collaborators.by_ref() {
-                if peer_id == leader_id {
-                    break;
-                }
-            }
-            collaborators.next()
-        } else if let Some(last_leader_id) =
-            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
-        {
-            if collaborators.contains_key(last_leader_id) {
-                Some(*last_leader_id)
-            } else {
-                None
-            }
-        } else {
-            None
-        };
-
-        let pane = self.active_pane.clone();
-        let Some(leader_id) = next_leader_id.or_else(|| collaborators.keys().copied().next())
-        else {
-            return;
-        };
-        if Some(leader_id) == self.unfollow(&pane, cx) {
-            return;
-        }
-        self.start_following(leader_id, cx)
-            .map(|task| task.detach_and_log_err(cx));
-    }
-
-    pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) {
-        let Some(room) = ActiveCall::global(cx).read(cx).room() else {
-            return;
-        };
-        let room = room.read(cx);
-        let Some(remote_participant) = room.remote_participant_for_peer_id(leader_id) else {
-            return;
-        };
-
-        let project = self.project.read(cx);
-
-        let other_project_id = match remote_participant.location {
-            call::ParticipantLocation::External => None,
-            call::ParticipantLocation::UnsharedProject => None,
-            call::ParticipantLocation::SharedProject { project_id } => {
-                if Some(project_id) == project.remote_id() {
-                    None
-                } else {
-                    Some(project_id)
-                }
-            }
-        };
-
-        // if they are active in another project, follow there.
-        if let Some(project_id) = other_project_id {
-            let app_state = self.app_state.clone();
-            crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
-                .detach_and_log_err(cx);
-        }
-
-        // if you're already following, find the right pane and focus it.
-        for (pane, state) in &self.follower_states {
-            if leader_id == state.leader_id {
-                cx.focus_view(pane);
-                return;
-            }
-        }
-
-        // Otherwise, follow.
-        self.start_following(leader_id, cx)
-            .map(|task| task.detach_and_log_err(cx));
-    }
-
-    pub fn unfollow(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Option<PeerId> {
-        let state = self.follower_states.remove(pane)?;
-        let leader_id = state.leader_id;
-        for (_, item) in state.items_by_leader_view_id {
-            item.set_leader_peer_id(None, cx);
-        }
-
-        if self
-            .follower_states
-            .values()
-            .all(|state| state.leader_id != state.leader_id)
-        {
-            let project_id = self.project.read(cx).remote_id();
-            let room_id = self.active_call()?.read(cx).room()?.read(cx).id();
-            self.app_state
-                .client
-                .send(proto::Unfollow {
-                    room_id,
-                    project_id,
-                    leader_id: Some(leader_id),
-                })
-                .log_err();
-        }
-
-        cx.notify();
-        Some(leader_id)
-    }
-
-    //     pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
-    //         self.follower_states
-    //             .values()
-    //             .any(|state| state.leader_id == peer_id)
-    //     }
-
-    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
-        let active_entry = self.active_project_path(cx);
-        self.project
-            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
-        self.update_window_title(cx);
-    }
-
-    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
-        let project = self.project().read(cx);
-        let mut title = String::new();
-
-        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
-            let filename = path
-                .path
-                .file_name()
-                .map(|s| s.to_string_lossy())
-                .or_else(|| {
-                    Some(Cow::Borrowed(
-                        project
-                            .worktree_for_id(path.worktree_id, cx)?
-                            .read(cx)
-                            .root_name(),
-                    ))
-                });
-
-            if let Some(filename) = filename {
-                title.push_str(filename.as_ref());
-                title.push_str(" β€” ");
-            }
-        }
-
-        for (i, name) in project.worktree_root_names(cx).enumerate() {
-            if i > 0 {
-                title.push_str(", ");
-            }
-            title.push_str(name);
-        }
-
-        if title.is_empty() {
-            title = "empty project".to_string();
-        }
-
-        if project.is_remote() {
-            title.push_str(" ↙");
-        } else if project.is_shared() {
-            title.push_str(" β†—");
-        }
-
-        cx.set_window_title(&title);
-    }
-
-    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
-        let is_edited = !self.project.read(cx).is_read_only()
-            && self
-                .items(cx)
-                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
-        if is_edited != self.window_edited {
-            self.window_edited = is_edited;
-            cx.set_window_edited(self.window_edited)
-        }
-    }
-
-    fn render_notifications(&self, _cx: &ViewContext<Self>) -> Option<Div> {
-        if self.notifications.is_empty() {
-            None
-        } else {
-            Some(
-                div()
-                    .absolute()
-                    .z_index(100)
-                    .right_3()
-                    .bottom_3()
-                    .w_96()
-                    .h_full()
-                    .flex()
-                    .flex_col()
-                    .justify_end()
-                    .gap_2()
-                    .children(
-                        self.notifications
-                            .iter()
-                            .map(|(_, _, notification)| notification.to_any()),
-                    ),
-            )
-        }
-    }
-
-    // RPC handlers
-
-    fn handle_follow(
-        &mut self,
-        follower_project_id: Option<u64>,
-        cx: &mut ViewContext<Self>,
-    ) -> proto::FollowResponse {
-        let client = &self.app_state.client;
-        let project_id = self.project.read(cx).remote_id();
-
-        let active_view_id = self.active_item(cx).and_then(|i| {
-            Some(
-                i.to_followable_item_handle(cx)?
-                    .remote_id(client, cx)?
-                    .to_proto(),
-            )
-        });
-
-        cx.notify();
-
-        self.last_active_view_id = active_view_id.clone();
-        proto::FollowResponse {
-            active_view_id,
-            views: self
-                .panes()
-                .iter()
-                .flat_map(|pane| {
-                    let leader_id = self.leader_for_pane(pane);
-                    pane.read(cx).items().filter_map({
-                        let cx = &cx;
-                        move |item| {
-                            let item = item.to_followable_item_handle(cx)?;
-                            if (project_id.is_none() || project_id != follower_project_id)
-                                && item.is_project_item(cx)
-                            {
-                                return None;
-                            }
-                            let id = item.remote_id(client, cx)?.to_proto();
-                            let variant = item.to_state_proto(cx)?;
-                            Some(proto::View {
-                                id: Some(id),
-                                leader_id,
-                                variant: Some(variant),
-                            })
-                        }
-                    })
-                })
-                .collect(),
-        }
-    }
-
-    fn handle_update_followers(
-        &mut self,
-        leader_id: PeerId,
-        message: proto::UpdateFollowers,
-        _cx: &mut ViewContext<Self>,
-    ) {
-        self.leader_updates_tx
-            .unbounded_send((leader_id, message))
-            .ok();
-    }
-
-    async fn process_leader_update(
-        this: &WeakView<Self>,
-        leader_id: PeerId,
-        update: proto::UpdateFollowers,
-        cx: &mut AsyncWindowContext,
-    ) -> Result<()> {
-        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
-            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
-                this.update(cx, |this, _| {
-                    for (_, state) in &mut this.follower_states {
-                        if state.leader_id == leader_id {
-                            state.active_view_id =
-                                if let Some(active_view_id) = update_active_view.id.clone() {
-                                    Some(ViewId::from_proto(active_view_id)?)
-                                } else {
-                                    None
-                                };
-                        }
-                    }
-                    anyhow::Ok(())
-                })??;
-            }
-            proto::update_followers::Variant::UpdateView(update_view) => {
-                let variant = update_view
-                    .variant
-                    .ok_or_else(|| anyhow!("missing update view variant"))?;
-                let id = update_view
-                    .id
-                    .ok_or_else(|| anyhow!("missing update view id"))?;
-                let mut tasks = Vec::new();
-                this.update(cx, |this, cx| {
-                    let project = this.project.clone();
-                    for (_, state) in &mut this.follower_states {
-                        if state.leader_id == leader_id {
-                            let view_id = ViewId::from_proto(id.clone())?;
-                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
-                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
-                            }
-                        }
-                    }
-                    anyhow::Ok(())
-                })??;
-                try_join_all(tasks).await.log_err();
-            }
-            proto::update_followers::Variant::CreateView(view) => {
-                let panes = this.update(cx, |this, _| {
-                    this.follower_states
-                        .iter()
-                        .filter_map(|(pane, state)| (state.leader_id == leader_id).then_some(pane))
-                        .cloned()
-                        .collect()
-                })?;
-                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
-            }
-        }
-        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
-        Ok(())
-    }
-
-    async fn add_views_from_leader(
-        this: WeakView<Self>,
-        leader_id: PeerId,
-        panes: Vec<View<Pane>>,
-        views: Vec<proto::View>,
-        cx: &mut AsyncWindowContext,
-    ) -> Result<()> {
-        let this = this.upgrade().context("workspace dropped")?;
-
-        let item_builders = cx.update(|_, cx| {
-            cx.default_global::<FollowableItemBuilders>()
-                .values()
-                .map(|b| b.0)
-                .collect::<Vec<_>>()
-        })?;
-
-        let mut item_tasks_by_pane = HashMap::default();
-        for pane in panes {
-            let mut item_tasks = Vec::new();
-            let mut leader_view_ids = Vec::new();
-            for view in &views {
-                let Some(id) = &view.id else { continue };
-                let id = ViewId::from_proto(id.clone())?;
-                let mut variant = view.variant.clone();
-                if variant.is_none() {
-                    Err(anyhow!("missing view variant"))?;
-                }
-                for build_item in &item_builders {
-                    let task = cx.update(|_, cx| {
-                        build_item(pane.clone(), this.clone(), id, &mut variant, cx)
-                    })?;
-                    if let Some(task) = task {
-                        item_tasks.push(task);
-                        leader_view_ids.push(id);
-                        break;
-                    } else {
-                        assert!(variant.is_some());
-                    }
-                }
-            }
-
-            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
-        }
-
-        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
-            let items = futures::future::try_join_all(item_tasks).await?;
-            this.update(cx, |this, cx| {
-                let state = this.follower_states.get_mut(&pane)?;
-                for (id, item) in leader_view_ids.into_iter().zip(items) {
-                    item.set_leader_peer_id(Some(leader_id), cx);
-                    state.items_by_leader_view_id.insert(id, item);
-                }
-
-                Some(())
-            })?;
-        }
-        Ok(())
-    }
-
-    fn update_active_view_for_followers(&mut self, cx: &mut ViewContext<Self>) {
-        let mut is_project_item = true;
-        let mut update = proto::UpdateActiveView::default();
-
-        if let Some(item) = self.active_item(cx) {
-            if item.focus_handle(cx).contains_focused(cx) {
-                if let Some(item) = item.to_followable_item_handle(cx) {
-                    is_project_item = item.is_project_item(cx);
-                    update = proto::UpdateActiveView {
-                        id: item
-                            .remote_id(&self.app_state.client, cx)
-                            .map(|id| id.to_proto()),
-                        leader_id: self.leader_for_pane(&self.active_pane),
-                    };
-                }
-            }
-        }
-
-        if update.id != self.last_active_view_id {
-            self.last_active_view_id = update.id.clone();
-            self.update_followers(
-                is_project_item,
-                proto::update_followers::Variant::UpdateActiveView(update),
-                cx,
-            );
-        }
-    }
-
-    fn update_followers(
-        &self,
-        project_only: bool,
-        update: proto::update_followers::Variant,
-        cx: &mut WindowContext,
-    ) -> Option<()> {
-        let project_id = if project_only {
-            self.project.read(cx).remote_id()
-        } else {
-            None
-        };
-        self.app_state().workspace_store.update(cx, |store, cx| {
-            store.update_followers(project_id, update, cx)
-        })
-    }
-
-    pub fn leader_for_pane(&self, pane: &View<Pane>) -> Option<PeerId> {
-        self.follower_states.get(pane).map(|state| state.leader_id)
-    }
-
-    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
-        cx.notify();
-
-        let call = self.active_call()?;
-        let room = call.read(cx).room()?.read(cx);
-        let participant = room.remote_participant_for_peer_id(leader_id)?;
-        let mut items_to_activate = Vec::new();
-
-        let leader_in_this_app;
-        let leader_in_this_project;
-        match participant.location {
-            call::ParticipantLocation::SharedProject { project_id } => {
-                leader_in_this_app = true;
-                leader_in_this_project = Some(project_id) == self.project.read(cx).remote_id();
-            }
-            call::ParticipantLocation::UnsharedProject => {
-                leader_in_this_app = true;
-                leader_in_this_project = false;
-            }
-            call::ParticipantLocation::External => {
-                leader_in_this_app = false;
-                leader_in_this_project = false;
-            }
-        };
-
-        for (pane, state) in &self.follower_states {
-            if state.leader_id != leader_id {
-                continue;
-            }
-            if let (Some(active_view_id), true) = (state.active_view_id, leader_in_this_app) {
-                if let Some(item) = state.items_by_leader_view_id.get(&active_view_id) {
-                    if leader_in_this_project || !item.is_project_item(cx) {
-                        items_to_activate.push((pane.clone(), item.boxed_clone()));
-                    }
-                }
-                continue;
-            }
-
-            if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
-                items_to_activate.push((pane.clone(), Box::new(shared_screen)));
-            }
-        }
-
-        for (pane, item) in items_to_activate {
-            let pane_was_focused = pane.read(cx).has_focus(cx);
-            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
-                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
-            } else {
-                pane.update(cx, |pane, cx| {
-                    pane.add_item(item.boxed_clone(), false, false, None, cx)
-                });
-            }
-
-            if pane_was_focused {
-                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
-            }
-        }
-
-        None
-    }
-
-    fn shared_screen_for_peer(
-        &self,
-        peer_id: PeerId,
-        pane: &View<Pane>,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<View<SharedScreen>> {
-        let call = self.active_call()?;
-        let room = call.read(cx).room()?.read(cx);
-        let participant = room.remote_participant_for_peer_id(peer_id)?;
-        let track = participant.video_tracks.values().next()?.clone();
-        let user = participant.user.clone();
-
-        for item in pane.read(cx).items_of_type::<SharedScreen>() {
-            if item.read(cx).peer_id == peer_id {
-                return Some(item);
-            }
-        }
-
-        Some(cx.new_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
-    }
-
-    pub fn on_window_activation_changed(&mut self, cx: &mut ViewContext<Self>) {
-        if cx.is_window_active() {
-            self.update_active_view_for_followers(cx);
-            cx.background_executor()
-                .spawn(persistence::DB.update_timestamp(self.database_id()))
-                .detach();
-        } else {
-            for pane in &self.panes {
-                pane.update(cx, |pane, cx| {
-                    if let Some(item) = pane.active_item() {
-                        item.workspace_deactivated(cx);
-                    }
-                    if matches!(
-                        WorkspaceSettings::get_global(cx).autosave,
-                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
-                    ) {
-                        for item in pane.items() {
-                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
-                                .detach_and_log_err(cx);
-                        }
-                    }
-                });
-            }
-        }
-    }
-
-    fn active_call(&self) -> Option<&Model<ActiveCall>> {
-        self.active_call.as_ref().map(|(call, _)| call)
-    }
-
-    fn on_active_call_event(
-        &mut self,
-        _: Model<ActiveCall>,
-        event: &call::room::Event,
-        cx: &mut ViewContext<Self>,
-    ) {
-        match event {
-            call::room::Event::ParticipantLocationChanged { participant_id }
-            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
-                self.leader_updated(*participant_id, cx);
-            }
-            _ => {}
-        }
-    }
-
-    pub fn database_id(&self) -> WorkspaceId {
-        self.database_id
-    }
-
-    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
-        let project = self.project().read(cx);
-
-        if project.is_local() {
-            Some(
-                project
-                    .visible_worktrees(cx)
-                    .map(|worktree| worktree.read(cx).abs_path())
-                    .collect::<Vec<_>>()
-                    .into(),
-            )
-        } else {
-            None
-        }
-    }
-
-    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
-        match member {
-            Member::Axis(PaneAxis { members, .. }) => {
-                for child in members.iter() {
-                    self.remove_panes(child.clone(), cx)
-                }
-            }
-            Member::Pane(pane) => {
-                self.force_remove_pane(&pane, cx);
-            }
-        }
-    }
-
-    fn force_remove_pane(&mut self, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
-        self.panes.retain(|p| p != pane);
-        self.panes
-            .last()
-            .unwrap()
-            .update(cx, |pane, cx| pane.focus(cx));
-        if self.last_active_center_pane == Some(pane.downgrade()) {
-            self.last_active_center_pane = None;
-        }
-        cx.notify();
-    }
-
-    #[allow(unused)]
-    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
-        self._schedule_serialize = Some(cx.spawn(|this, mut cx| async move {
-            cx.background_executor()
-                .timer(Duration::from_millis(100))
-                .await;
-            this.update(&mut cx, |this, cx| this.serialize_workspace(cx))
-                .log_err();
-        }));
-    }
-
-    fn serialize_workspace(&self, cx: &mut ViewContext<Self>) {
-        fn serialize_pane_handle(pane_handle: &View<Pane>, cx: &WindowContext) -> SerializedPane {
-            let (items, active) = {
-                let pane = pane_handle.read(cx);
-                let active_item_id = pane.active_item().map(|item| item.item_id());
-                (
-                    pane.items()
-                        .filter_map(|item_handle| {
-                            Some(SerializedItem {
-                                kind: Arc::from(item_handle.serialized_item_kind()?),
-                                item_id: item_handle.item_id().as_u64(),
-                                active: Some(item_handle.item_id()) == active_item_id,
-                            })
-                        })
-                        .collect::<Vec<_>>(),
-                    pane.has_focus(cx),
-                )
-            };
-
-            SerializedPane::new(items, active)
-        }
-
-        fn build_serialized_pane_group(
-            pane_group: &Member,
-            cx: &WindowContext,
-        ) -> SerializedPaneGroup {
-            match pane_group {
-                Member::Axis(PaneAxis {
-                    axis,
-                    members,
-                    flexes,
-                    bounding_boxes: _,
-                }) => SerializedPaneGroup::Group {
-                    axis: *axis,
-                    children: members
-                        .iter()
-                        .map(|member| build_serialized_pane_group(member, cx))
-                        .collect::<Vec<_>>(),
-                    flexes: Some(flexes.lock().clone()),
-                },
-                Member::Pane(pane_handle) => {
-                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
-                }
-            }
-        }
-
-        fn build_serialized_docks(
-            this: &Workspace,
-            cx: &mut ViewContext<Workspace>,
-        ) -> DockStructure {
-            let left_dock = this.left_dock.read(cx);
-            let left_visible = left_dock.is_open();
-            let left_active_panel = left_dock
-                .visible_panel()
-                .and_then(|panel| Some(panel.persistent_name().to_string()));
-            let left_dock_zoom = left_dock
-                .visible_panel()
-                .map(|panel| panel.is_zoomed(cx))
-                .unwrap_or(false);
-
-            let right_dock = this.right_dock.read(cx);
-            let right_visible = right_dock.is_open();
-            let right_active_panel = right_dock
-                .visible_panel()
-                .and_then(|panel| Some(panel.persistent_name().to_string()));
-            let right_dock_zoom = right_dock
-                .visible_panel()
-                .map(|panel| panel.is_zoomed(cx))
-                .unwrap_or(false);
-
-            let bottom_dock = this.bottom_dock.read(cx);
-            let bottom_visible = bottom_dock.is_open();
-            let bottom_active_panel = bottom_dock
-                .visible_panel()
-                .and_then(|panel| Some(panel.persistent_name().to_string()));
-            let bottom_dock_zoom = bottom_dock
-                .visible_panel()
-                .map(|panel| panel.is_zoomed(cx))
-                .unwrap_or(false);
-
-            DockStructure {
-                left: DockData {
-                    visible: left_visible,
-                    active_panel: left_active_panel,
-                    zoom: left_dock_zoom,
-                },
-                right: DockData {
-                    visible: right_visible,
-                    active_panel: right_active_panel,
-                    zoom: right_dock_zoom,
-                },
-                bottom: DockData {
-                    visible: bottom_visible,
-                    active_panel: bottom_active_panel,
-                    zoom: bottom_dock_zoom,
-                },
-            }
-        }
-
-        if let Some(location) = self.location(cx) {
-            // Load bearing special case:
-            //  - with_local_workspace() relies on this to not have other stuff open
-            //    when you open your log
-            if !location.paths().is_empty() {
-                let center_group = build_serialized_pane_group(&self.center.root, cx);
-                let docks = build_serialized_docks(self, cx);
-
-                let serialized_workspace = SerializedWorkspace {
-                    id: self.database_id,
-                    location,
-                    center_group,
-                    bounds: Default::default(),
-                    display: Default::default(),
-                    docks,
-                };
-
-                cx.spawn(|_, _| persistence::DB.save_workspace(serialized_workspace))
-                    .detach();
-            }
-        }
-    }
-
-    pub(crate) fn load_workspace(
-        serialized_workspace: SerializedWorkspace,
-        paths_to_open: Vec<Option<ProjectPath>>,
-        cx: &mut ViewContext<Workspace>,
-    ) -> Task<Result<Vec<Option<Box<dyn ItemHandle>>>>> {
-        cx.spawn(|workspace, mut cx| async move {
-            let project = workspace.update(&mut cx, |workspace, _| workspace.project().clone())?;
-
-            let mut center_group = None;
-            let mut center_items = None;
-
-            // Traverse the splits tree and add to things
-            if let Some((group, active_pane, items)) = serialized_workspace
-                .center_group
-                .deserialize(
-                    &project,
-                    serialized_workspace.id,
-                    workspace.clone(),
-                    &mut cx,
-                )
-                .await
-            {
-                center_items = Some(items);
-                center_group = Some((group, active_pane))
-            }
-
-            let mut items_by_project_path = cx.update(|_, cx| {
-                center_items
-                    .unwrap_or_default()
-                    .into_iter()
-                    .filter_map(|item| {
-                        let item = item?;
-                        let project_path = item.project_path(cx)?;
-                        Some((project_path, item))
-                    })
-                    .collect::<HashMap<_, _>>()
-            })?;
-
-            let opened_items = paths_to_open
-                .into_iter()
-                .map(|path_to_open| {
-                    path_to_open
-                        .and_then(|path_to_open| items_by_project_path.remove(&path_to_open))
-                })
-                .collect::<Vec<_>>();
-
-            // Remove old panes from workspace panes list
-            workspace.update(&mut cx, |workspace, cx| {
-                if let Some((center_group, active_pane)) = center_group {
-                    workspace.remove_panes(workspace.center.root.clone(), cx);
-
-                    // Swap workspace center group
-                    workspace.center = PaneGroup::with_root(center_group);
-                    workspace.last_active_center_pane = active_pane.as_ref().map(|p| p.downgrade());
-                    if let Some(active_pane) = active_pane {
-                        workspace.active_pane = active_pane;
-                        cx.focus_self();
-                    } else {
-                        workspace.active_pane = workspace.center.first_pane().clone();
-                    }
-                }
-
-                let docks = serialized_workspace.docks;
-                workspace.left_dock.update(cx, |dock, cx| {
-                    dock.set_open(docks.left.visible, cx);
-                    if let Some(active_panel) = docks.left.active_panel {
-                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
-                            dock.activate_panel(ix, cx);
-                        }
-                    }
-                    dock.active_panel()
-                        .map(|panel| panel.set_zoomed(docks.left.zoom, cx));
-                    if docks.left.visible && docks.left.zoom {
-                        cx.focus_self()
-                    }
-                });
-                // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
-                workspace.right_dock.update(cx, |dock, cx| {
-                    dock.set_open(docks.right.visible, cx);
-                    if let Some(active_panel) = docks.right.active_panel {
-                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
-                            dock.activate_panel(ix, cx);
-                        }
-                    }
-                    dock.active_panel()
-                        .map(|panel| panel.set_zoomed(docks.right.zoom, cx));
-
-                    if docks.right.visible && docks.right.zoom {
-                        cx.focus_self()
-                    }
-                });
-                workspace.bottom_dock.update(cx, |dock, cx| {
-                    dock.set_open(docks.bottom.visible, cx);
-                    if let Some(active_panel) = docks.bottom.active_panel {
-                        if let Some(ix) = dock.panel_index_for_persistent_name(&active_panel, cx) {
-                            dock.activate_panel(ix, cx);
-                        }
-                    }
-
-                    dock.active_panel()
-                        .map(|panel| panel.set_zoomed(docks.bottom.zoom, cx));
-
-                    if docks.bottom.visible && docks.bottom.zoom {
-                        cx.focus_self()
-                    }
-                });
-
-                cx.notify();
-            })?;
-
-            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
-            workspace.update(&mut cx, |workspace, cx| workspace.serialize_workspace(cx))?;
-
-            Ok(opened_items)
-        })
-    }
-
-    fn actions(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
-        self.add_workspace_actions_listeners(div, cx)
-            .on_action(cx.listener(Self::close_inactive_items_and_panes))
-            .on_action(cx.listener(Self::close_all_items_and_panes))
-            .on_action(cx.listener(Self::save_all))
-            .on_action(cx.listener(Self::add_folder_to_project))
-            .on_action(cx.listener(Self::follow_next_collaborator))
-            .on_action(cx.listener(|workspace, _: &Unfollow, cx| {
-                let pane = workspace.active_pane().clone();
-                workspace.unfollow(&pane, cx);
-            }))
-            .on_action(cx.listener(|workspace, action: &Save, cx| {
-                workspace
-                    .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
-                    .detach_and_log_err(cx);
-            }))
-            .on_action(cx.listener(|workspace, _: &SaveAs, cx| {
-                workspace
-                    .save_active_item(SaveIntent::SaveAs, cx)
-                    .detach_and_log_err(cx);
-            }))
-            .on_action(cx.listener(|workspace, _: &ActivatePreviousPane, cx| {
-                workspace.activate_previous_pane(cx)
-            }))
-            .on_action(
-                cx.listener(|workspace, _: &ActivateNextPane, cx| workspace.activate_next_pane(cx)),
-            )
-            .on_action(
-                cx.listener(|workspace, action: &ActivatePaneInDirection, cx| {
-                    workspace.activate_pane_in_direction(action.0, cx)
-                }),
-            )
-            .on_action(cx.listener(|workspace, action: &SwapPaneInDirection, cx| {
-                workspace.swap_pane_in_direction(action.0, cx)
-            }))
-            .on_action(cx.listener(|this, _: &ToggleLeftDock, cx| {
-                this.toggle_dock(DockPosition::Left, cx);
-            }))
-            .on_action(
-                cx.listener(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
-                    workspace.toggle_dock(DockPosition::Right, cx);
-                }),
-            )
-            .on_action(
-                cx.listener(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
-                    workspace.toggle_dock(DockPosition::Bottom, cx);
-                }),
-            )
-            .on_action(
-                cx.listener(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
-                    workspace.close_all_docks(cx);
-                }),
-            )
-            .on_action(cx.listener(Workspace::open))
-            .on_action(cx.listener(Workspace::close_window))
-            .on_action(cx.listener(Workspace::activate_pane_at_index))
-            .on_action(
-                cx.listener(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
-                    workspace.reopen_closed_item(cx).detach();
-                }),
-            )
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test_new(project: Model<Project>, cx: &mut ViewContext<Self>) -> Self {
-        use node_runtime::FakeNodeRuntime;
-
-        let client = project.read(cx).client();
-        let user_store = project.read(cx).user_store();
-
-        let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
-        let app_state = Arc::new(AppState {
-            languages: project.read(cx).languages().clone(),
-            workspace_store,
-            client,
-            user_store,
-            fs: project.read(cx).fs().clone(),
-            build_window_options: |_, _, _| Default::default(),
-            node_runtime: FakeNodeRuntime::new(),
-        });
-        let workspace = Self::new(0, project, app_state, cx);
-        workspace.active_pane.update(cx, |pane, cx| pane.focus(cx));
-        workspace
-    }
-
-    //     fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
-    //         let dock = match position {
-    //             DockPosition::Left => &self.left_dock,
-    //             DockPosition::Right => &self.right_dock,
-    //             DockPosition::Bottom => &self.bottom_dock,
-    //         };
-    //         let active_panel = dock.read(cx).visible_panel()?;
-    //         let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
-    //             dock.read(cx).render_placeholder(cx)
-    //         } else {
-    //             ChildView::new(dock, cx).into_any()
-    //         };
-
-    //         Some(
-    //             element
-    //                 .constrained()
-    //                 .dynamically(move |constraint, _, cx| match position {
-    //                     DockPosition::Left | DockPosition::Right => SizeConstraint::new(
-    //                         Vector2F::new(20., constraint.min.y()),
-    //                         Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
-    //                     ),
-    //                     DockPosition::Bottom => SizeConstraint::new(
-    //                         Vector2F::new(constraint.min.x(), 20.),
-    //                         Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
-    //                     ),
-    //                 })
-    //                 .into_any(),
-    //         )
-    //     }
-    // }
-    pub fn register_action<A: Action>(
-        &mut self,
-        callback: impl Fn(&mut Self, &A, &mut ViewContext<Self>) + 'static,
-    ) -> &mut Self {
-        let callback = Arc::new(callback);
-
-        self.workspace_actions.push(Box::new(move |div, cx| {
-            let callback = callback.clone();
-            div.on_action(
-                cx.listener(move |workspace, event, cx| (callback.clone())(workspace, event, cx)),
-            )
-        }));
-        self
-    }
-
-    fn add_workspace_actions_listeners(&self, div: Div, cx: &mut ViewContext<Self>) -> Div {
-        let mut div = div
-            .on_action(cx.listener(Self::close_inactive_items_and_panes))
-            .on_action(cx.listener(Self::close_all_items_and_panes))
-            .on_action(cx.listener(Self::add_folder_to_project))
-            .on_action(cx.listener(Self::save_all))
-            .on_action(cx.listener(Self::open));
-        for action in self.workspace_actions.iter() {
-            div = (action)(div, cx)
-        }
-        div
-    }
-
-    pub fn active_modal<V: ManagedView + 'static>(
-        &mut self,
-        cx: &ViewContext<Self>,
-    ) -> Option<View<V>> {
-        self.modal_layer.read(cx).active_modal()
-    }
-
-    pub fn toggle_modal<V: ModalView, B>(&mut self, cx: &mut ViewContext<Self>, build: B)
-    where
-        B: FnOnce(&mut ViewContext<V>) -> V,
-    {
-        self.modal_layer
-            .update(cx, |modal_layer, cx| modal_layer.toggle_modal(cx, build))
-    }
-}
-
-fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
-    let display_origin = cx
-        .update(|cx| Some(cx.displays().first()?.bounds().origin))
-        .ok()??;
-    ZED_WINDOW_POSITION
-        .zip(*ZED_WINDOW_SIZE)
-        .map(|(position, size)| {
-            WindowBounds::Fixed(Bounds {
-                origin: display_origin + position,
-                size,
-            })
-        })
-}
-
-fn open_items(
-    serialized_workspace: Option<SerializedWorkspace>,
-    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
-    app_state: Arc<AppState>,
-    cx: &mut ViewContext<Workspace>,
-) -> impl 'static + Future<Output = Result<Vec<Option<Result<Box<dyn ItemHandle>>>>>> {
-    let restored_items = serialized_workspace.map(|serialized_workspace| {
-        Workspace::load_workspace(
-            serialized_workspace,
-            project_paths_to_open
-                .iter()
-                .map(|(_, project_path)| project_path)
-                .cloned()
-                .collect(),
-            cx,
-        )
-    });
-
-    cx.spawn(|workspace, mut cx| async move {
-        let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
-
-        if let Some(restored_items) = restored_items {
-            let restored_items = restored_items.await?;
-
-            let restored_project_paths = restored_items
-                .iter()
-                .filter_map(|item| {
-                    cx.update(|_, cx| item.as_ref()?.project_path(cx))
-                        .ok()
-                        .flatten()
-                })
-                .collect::<HashSet<_>>();
-
-            for restored_item in restored_items {
-                opened_items.push(restored_item.map(Ok));
-            }
-
-            project_paths_to_open
-                .iter_mut()
-                .for_each(|(_, project_path)| {
-                    if let Some(project_path_to_open) = project_path {
-                        if restored_project_paths.contains(project_path_to_open) {
-                            *project_path = None;
-                        }
-                    }
-                });
-        } else {
-            for _ in 0..project_paths_to_open.len() {
-                opened_items.push(None);
-            }
-        }
-        assert!(opened_items.len() == project_paths_to_open.len());
-
-        let tasks =
-            project_paths_to_open
-                .into_iter()
-                .enumerate()
-                .map(|(i, (abs_path, project_path))| {
-                    let workspace = workspace.clone();
-                    cx.spawn(|mut cx| {
-                        let fs = app_state.fs.clone();
-                        async move {
-                            let file_project_path = project_path?;
-                            if fs.is_file(&abs_path).await {
-                                Some((
-                                    i,
-                                    workspace
-                                        .update(&mut cx, |workspace, cx| {
-                                            workspace.open_path(file_project_path, None, true, cx)
-                                        })
-                                        .log_err()?
-                                        .await,
-                                ))
-                            } else {
-                                None
-                            }
-                        }
-                    })
-                });
-
-        let tasks = tasks.collect::<Vec<_>>();
-
-        let tasks = futures::future::join_all(tasks.into_iter());
-        for maybe_opened_path in tasks.await.into_iter() {
-            if let Some((i, path_open_result)) = maybe_opened_path {
-                opened_items[i] = Some(path_open_result);
-            }
-        }
-
-        Ok(opened_items)
-    })
-}
-
-fn notify_if_database_failed(workspace: WindowHandle<Workspace>, cx: &mut AsyncAppContext) {
-    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
-
-    workspace
-        .update(cx, |workspace, cx| {
-            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
-                workspace.show_notification_once(0, cx, |cx| {
-                    cx.new_view(|_| {
-                        MessageNotification::new("Failed to load the database file.")
-                            .with_click_message("Click to let us know about this error")
-                            .on_click(|cx| cx.open_url(REPORT_ISSUE_URL))
-                    })
-                });
-            }
-        })
-        .log_err();
-}
-
-impl FocusableView for Workspace {
-    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
-        self.active_pane.focus_handle(cx)
-    }
-}
-
-#[derive(Clone, Render)]
-struct DraggedDock(DockPosition);
-
-impl Render for Workspace {
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let mut context = KeyContext::default();
-        context.add("Workspace");
-
-        let (ui_font, ui_font_size) = {
-            let theme_settings = ThemeSettings::get_global(cx);
-            (
-                theme_settings.ui_font.family.clone(),
-                theme_settings.ui_font_size.clone(),
-            )
-        };
-
-        let theme = cx.theme().clone();
-        let colors = theme.colors();
-        cx.set_rem_size(ui_font_size);
-
-        self.actions(div(), cx)
-            .key_context(context)
-            .relative()
-            .size_full()
-            .flex()
-            .flex_col()
-            .font(ui_font)
-            .gap_0()
-            .justify_start()
-            .items_start()
-            .text_color(colors.text)
-            .bg(colors.background)
-            .border()
-            .border_color(colors.border)
-            .children(self.titlebar_item.clone())
-            .child(
-                div()
-                    .id("workspace")
-                    .relative()
-                    .flex_1()
-                    .w_full()
-                    .flex()
-                    .flex_col()
-                    .overflow_hidden()
-                    .border_t()
-                    .border_b()
-                    .border_color(colors.border)
-                    .child(
-                        canvas(cx.listener(|workspace, bounds, _| {
-                            workspace.bounds = *bounds;
-                        }))
-                        .absolute()
-                        .size_full(),
-                    )
-                    .on_drag_move(
-                        cx.listener(|workspace, e: &DragMoveEvent<DraggedDock>, cx| {
-                            match e.drag(cx).0 {
-                                DockPosition::Left => {
-                                    let size = workspace.bounds.left() + e.event.position.x;
-                                    workspace.left_dock.update(cx, |left_dock, cx| {
-                                        left_dock.resize_active_panel(Some(size), cx);
-                                    });
-                                }
-                                DockPosition::Right => {
-                                    let size = workspace.bounds.right() - e.event.position.x;
-                                    workspace.right_dock.update(cx, |right_dock, cx| {
-                                        right_dock.resize_active_panel(Some(size), cx);
-                                    });
-                                }
-                                DockPosition::Bottom => {
-                                    let size = workspace.bounds.bottom() - e.event.position.y;
-                                    workspace.bottom_dock.update(cx, |bottom_dock, cx| {
-                                        bottom_dock.resize_active_panel(Some(size), cx);
-                                    });
-                                }
-                            }
-                        }),
-                    )
-                    .child(self.modal_layer.clone())
-                    .child(
-                        div()
-                            .flex()
-                            .flex_row()
-                            .h_full()
-                            // Left Dock
-                            .children(self.zoomed_position.ne(&Some(DockPosition::Left)).then(
-                                || {
-                                    div()
-                                        .flex()
-                                        .flex_none()
-                                        .overflow_hidden()
-                                        .child(self.left_dock.clone())
-                                },
-                            ))
-                            // Panes
-                            .child(
-                                div()
-                                    .flex()
-                                    .flex_col()
-                                    .flex_1()
-                                    .overflow_hidden()
-                                    .child(self.center.render(
-                                        &self.project,
-                                        &self.follower_states,
-                                        self.active_call(),
-                                        &self.active_pane,
-                                        self.zoomed.as_ref(),
-                                        &self.app_state,
-                                        cx,
-                                    ))
-                                    .children(
-                                        self.zoomed_position
-                                            .ne(&Some(DockPosition::Bottom))
-                                            .then(|| self.bottom_dock.clone()),
-                                    ),
-                            )
-                            // Right Dock
-                            .children(self.zoomed_position.ne(&Some(DockPosition::Right)).then(
-                                || {
-                                    div()
-                                        .flex()
-                                        .flex_none()
-                                        .overflow_hidden()
-                                        .child(self.right_dock.clone())
-                                },
-                            )),
-                    )
-                    .children(self.render_notifications(cx))
-                    .children(self.zoomed.as_ref().and_then(|view| {
-                        let zoomed_view = view.upgrade()?;
-                        let div = div()
-                            .z_index(1)
-                            .absolute()
-                            .overflow_hidden()
-                            .border_color(colors.border)
-                            .bg(colors.background)
-                            .child(zoomed_view)
-                            .inset_0()
-                            .shadow_lg();
-
-                        Some(match self.zoomed_position {
-                            Some(DockPosition::Left) => div.right_2().border_r(),
-                            Some(DockPosition::Right) => div.left_2().border_l(),
-                            Some(DockPosition::Bottom) => div.top_2().border_t(),
-                            None => div.top_2().bottom_2().left_2().right_2().border(),
-                        })
-                    })),
-            )
-            .child(self.status_bar.clone())
-            .children(if self.project.read(cx).is_read_only() {
-                Some(DisconnectedOverlay)
-            } else {
-                None
-            })
-    }
-}
-
-impl WorkspaceStore {
-    pub fn new(client: Arc<Client>, cx: &mut ModelContext<Self>) -> Self {
-        Self {
-            workspaces: Default::default(),
-            followers: Default::default(),
-            _subscriptions: vec![
-                client.add_request_handler(cx.weak_model(), Self::handle_follow),
-                client.add_message_handler(cx.weak_model(), Self::handle_unfollow),
-                client.add_message_handler(cx.weak_model(), Self::handle_update_followers),
-            ],
-            client,
-        }
-    }
-
-    pub fn update_followers(
-        &self,
-        project_id: Option<u64>,
-        update: proto::update_followers::Variant,
-        cx: &AppContext,
-    ) -> Option<()> {
-        if !cx.has_global::<Model<ActiveCall>>() {
-            return None;
-        }
-
-        let room_id = ActiveCall::global(cx).read(cx).room()?.read(cx).id();
-        let follower_ids: Vec<_> = self
-            .followers
-            .iter()
-            .filter_map(|follower| {
-                if follower.project_id == project_id || project_id.is_none() {
-                    Some(follower.peer_id.into())
-                } else {
-                    None
-                }
-            })
-            .collect();
-        if follower_ids.is_empty() {
-            return None;
-        }
-        self.client
-            .send(proto::UpdateFollowers {
-                room_id,
-                project_id,
-                follower_ids,
-                variant: Some(update),
-            })
-            .log_err()
-    }
-
-    pub async fn handle_follow(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::Follow>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<proto::FollowResponse> {
-        this.update(&mut cx, |this, cx| {
-            let follower = Follower {
-                project_id: envelope.payload.project_id,
-                peer_id: envelope.original_sender_id()?,
-            };
-            let active_project = ActiveCall::global(cx).read(cx).location().cloned();
-
-            let mut response = proto::FollowResponse::default();
-            this.workspaces.retain(|workspace| {
-                workspace
-                    .update(cx, |workspace, cx| {
-                        let handler_response = workspace.handle_follow(follower.project_id, cx);
-                        if response.views.is_empty() {
-                            response.views = handler_response.views;
-                        } else {
-                            response.views.extend_from_slice(&handler_response.views);
-                        }
-
-                        if let Some(active_view_id) = handler_response.active_view_id.clone() {
-                            if response.active_view_id.is_none()
-                                || Some(workspace.project.downgrade()) == active_project
-                            {
-                                response.active_view_id = Some(active_view_id);
-                            }
-                        }
-                    })
-                    .is_ok()
-            });
-
-            if let Err(ix) = this.followers.binary_search(&follower) {
-                this.followers.insert(ix, follower);
-            }
-
-            Ok(response)
-        })?
-    }
-
-    async fn handle_unfollow(
-        model: Model<Self>,
-        envelope: TypedEnvelope<proto::Unfollow>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        model.update(&mut cx, |this, _| {
-            let follower = Follower {
-                project_id: envelope.payload.project_id,
-                peer_id: envelope.original_sender_id()?,
-            };
-            if let Ok(ix) = this.followers.binary_search(&follower) {
-                this.followers.remove(ix);
-            }
-            Ok(())
-        })?
-    }
-
-    async fn handle_update_followers(
-        this: Model<Self>,
-        envelope: TypedEnvelope<proto::UpdateFollowers>,
-        _: Arc<Client>,
-        mut cx: AsyncAppContext,
-    ) -> Result<()> {
-        let leader_id = envelope.original_sender_id()?;
-        let update = envelope.payload;
-
-        this.update(&mut cx, |this, cx| {
-            this.workspaces.retain(|workspace| {
-                workspace
-                    .update(cx, |workspace, cx| {
-                        let project_id = workspace.project.read(cx).remote_id();
-                        if update.project_id != project_id && update.project_id.is_some() {
-                            return;
-                        }
-                        workspace.handle_update_followers(leader_id, update.clone(), cx);
-                    })
-                    .is_ok()
-            });
-            Ok(())
-        })?
-    }
-}
-
-impl ViewId {
-    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
-        Ok(Self {
-            creator: message
-                .creator
-                .ok_or_else(|| anyhow!("creator is missing"))?,
-            id: message.id,
-        })
-    }
-
-    pub(crate) fn to_proto(&self) -> proto::ViewId {
-        proto::ViewId {
-            creator: Some(self.creator),
-            id: self.id,
-        }
-    }
-}
-
-pub trait WorkspaceHandle {
-    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
-}
-
-impl WorkspaceHandle for View<Workspace> {
-    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
-        self.read(cx)
-            .worktrees(cx)
-            .flat_map(|worktree| {
-                let worktree_id = worktree.read(cx).id();
-                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
-                    worktree_id,
-                    path: f.path.clone(),
-                })
-            })
-            .collect::<Vec<_>>()
-    }
-}
-
-impl std::fmt::Debug for OpenPaths {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("OpenPaths")
-            .field("paths", &self.paths)
-            .finish()
-    }
-}
-
-pub fn activate_workspace_for_project(
-    cx: &mut AppContext,
-    predicate: impl Fn(&Project, &AppContext) -> bool + Send + 'static,
-) -> Option<WindowHandle<Workspace>> {
-    for window in cx.windows() {
-        let Some(workspace) = window.downcast::<Workspace>() else {
-            continue;
-        };
-
-        let predicate = workspace
-            .update(cx, |workspace, cx| {
-                let project = workspace.project.read(cx);
-                if predicate(project, cx) {
-                    cx.activate_window();
-                    true
-                } else {
-                    false
-                }
-            })
-            .log_err()
-            .unwrap_or(false);
-
-        if predicate {
-            return Some(workspace);
-        }
-    }
-
-    None
-}
-
-pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
-    DB.last_workspace().await.log_err().flatten()
-}
-
-async fn join_channel_internal(
-    channel_id: u64,
-    app_state: &Arc<AppState>,
-    requesting_window: Option<WindowHandle<Workspace>>,
-    active_call: &Model<ActiveCall>,
-    cx: &mut AsyncAppContext,
-) -> Result<bool> {
-    let (should_prompt, open_room) = active_call.update(cx, |active_call, cx| {
-        let Some(room) = active_call.room().map(|room| room.read(cx)) else {
-            return (false, None);
-        };
-
-        let already_in_channel = room.channel_id() == Some(channel_id);
-        let should_prompt = room.is_sharing_project()
-            && room.remote_participants().len() > 0
-            && !already_in_channel;
-        let open_room = if already_in_channel {
-            active_call.room().cloned()
-        } else {
-            None
-        };
-        (should_prompt, open_room)
-    })?;
-
-    if let Some(room) = open_room {
-        let task = room.update(cx, |room, cx| {
-            if let Some((project, host)) = room.most_active_project(cx) {
-                return Some(join_remote_project(project, host, app_state.clone(), cx));
-            }
-
-            None
-        })?;
-        if let Some(task) = task {
-            task.await?;
-        }
-        return anyhow::Ok(true);
-    }
-
-    if should_prompt {
-        if let Some(workspace) = requesting_window {
-            let answer  = workspace.update(cx, |_, cx| {
-                cx.prompt(
-                    PromptLevel::Warning,
-                    "Leaving this call will unshare your current project.\nDo you want to switch channels?",
-                    &["Yes, Join Channel", "Cancel"],
-                )
-            })?.await;
-
-            if answer == Ok(1) {
-                return Ok(false);
-            }
-        } else {
-            return Ok(false); // unreachable!() hopefully
-        }
-    }
-
-    let client = cx.update(|cx| active_call.read(cx).client())?;
-
-    let mut client_status = client.status();
-
-    // this loop will terminate within client::CONNECTION_TIMEOUT seconds.
-    'outer: loop {
-        let Some(status) = client_status.recv().await else {
-            return Err(anyhow!("error connecting"));
-        };
-
-        match status {
-            Status::Connecting
-            | Status::Authenticating
-            | Status::Reconnecting
-            | Status::Reauthenticating => continue,
-            Status::Connected { .. } => break 'outer,
-            Status::SignedOut => return Err(anyhow!("not signed in")),
-            Status::UpgradeRequired => return Err(anyhow!("zed is out of date")),
-            Status::ConnectionError | Status::ConnectionLost | Status::ReconnectionError { .. } => {
-                return Err(anyhow!("zed is offline"))
-            }
-        }
-    }
-
-    let room = active_call
-        .update(cx, |active_call, cx| {
-            active_call.join_channel(channel_id, cx)
-        })?
-        .await?;
-
-    let Some(room) = room else {
-        return anyhow::Ok(true);
-    };
-
-    room.update(cx, |room, _| room.room_update_completed())?
-        .await;
-
-    let task = room.update(cx, |room, cx| {
-        if let Some((project, host)) = room.most_active_project(cx) {
-            return Some(join_remote_project(project, host, app_state.clone(), cx));
-        }
-
-        None
-    })?;
-    if let Some(task) = task {
-        task.await?;
-        return anyhow::Ok(true);
-    }
-    anyhow::Ok(false)
-}
-
-pub fn join_channel(
-    channel_id: u64,
-    app_state: Arc<AppState>,
-    requesting_window: Option<WindowHandle<Workspace>>,
-    cx: &mut AppContext,
-) -> Task<Result<()>> {
-    let active_call = ActiveCall::global(cx);
-    cx.spawn(|mut cx| async move {
-        let result = join_channel_internal(
-            channel_id,
-            &app_state,
-            requesting_window,
-            &active_call,
-            &mut cx,
-        )
-        .await;
-
-        // join channel succeeded, and opened a window
-        if matches!(result, Ok(true)) {
-            return anyhow::Ok(());
-        }
-
-        if requesting_window.is_some() {
-            return anyhow::Ok(());
-        }
-
-        // find an existing workspace to focus and show call controls
-        let mut active_window = activate_any_workspace_window(&mut cx);
-        if active_window.is_none() {
-            // no open workspaces, make one to show the error in (blergh)
-            cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), requesting_window, cx))?
-                .await?;
-        }
-
-        active_window = activate_any_workspace_window(&mut cx);
-        let Some(active_window) = active_window else {
-            return anyhow::Ok(());
-        };
-
-        if let Err(err) = result {
-            active_window
-                .update(&mut cx, |_, cx| {
-                    cx.prompt(
-                        PromptLevel::Critical,
-                        &format!("Failed to join channel: {}", err),
-                        &["Ok"],
-                    )
-                })?
-                .await
-                .ok();
-        }
-
-        // return ok, we showed the error to the user.
-        return anyhow::Ok(());
-    })
-}
-
-pub async fn get_any_active_workspace(
-    app_state: Arc<AppState>,
-    mut cx: AsyncAppContext,
-) -> anyhow::Result<WindowHandle<Workspace>> {
-    // find an existing workspace to focus and show call controls
-    let active_window = activate_any_workspace_window(&mut cx);
-    if active_window.is_none() {
-        cx.update(|cx| Workspace::new_local(vec![], app_state.clone(), None, cx))?
-            .await?;
-    }
-    activate_any_workspace_window(&mut cx)
-        .context("could not open zed")?
-        .downcast::<Workspace>()
-        .context("could not open zed workspace window")
-}
-
-fn activate_any_workspace_window(cx: &mut AsyncAppContext) -> Option<AnyWindowHandle> {
-    cx.update(|cx| {
-        for window in cx.windows() {
-            let is_workspace = window.downcast::<Workspace>().is_some();
-            if is_workspace {
-                window.update(cx, |_, cx| cx.activate_window()).ok();
-                return Some(window);
-            }
-        }
-        None
-    })
-    .ok()
-    .flatten()
-}
-
-#[allow(clippy::type_complexity)]
-pub fn open_paths(
-    abs_paths: &[PathBuf],
-    app_state: &Arc<AppState>,
-    requesting_window: Option<WindowHandle<Workspace>>,
-    cx: &mut AppContext,
-) -> Task<
-    anyhow::Result<(
-        WindowHandle<Workspace>,
-        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
-    )>,
-> {
-    let app_state = app_state.clone();
-    let abs_paths = abs_paths.to_vec();
-    // Open paths in existing workspace if possible
-    let existing = activate_workspace_for_project(cx, {
-        let abs_paths = abs_paths.clone();
-        move |project, cx| project.contains_paths(&abs_paths, cx)
-    });
-    cx.spawn(move |mut cx| async move {
-        if let Some(existing) = existing {
-            Ok((
-                existing.clone(),
-                existing
-                    .update(&mut cx, |workspace, cx| {
-                        workspace.open_paths(abs_paths, true, cx)
-                    })?
-                    .await,
-            ))
-        } else {
-            cx.update(move |cx| {
-                Workspace::new_local(abs_paths, app_state.clone(), requesting_window, cx)
-            })?
-            .await
-        }
-    })
-}
-
-pub fn open_new(
-    app_state: &Arc<AppState>,
-    cx: &mut AppContext,
-    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
-) -> Task<()> {
-    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
-    cx.spawn(|mut cx| async move {
-        if let Some((workspace, opened_paths)) = task.await.log_err() {
-            workspace
-                .update(&mut cx, |workspace, cx| {
-                    if opened_paths.is_empty() {
-                        init(workspace, cx)
-                    }
-                })
-                .log_err();
-        }
-    })
-}
-
-pub fn create_and_open_local_file(
-    path: &'static Path,
-    cx: &mut ViewContext<Workspace>,
-    default_content: impl 'static + Send + FnOnce() -> Rope,
-) -> Task<Result<Box<dyn ItemHandle>>> {
-    cx.spawn(|workspace, mut cx| async move {
-        let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
-        if !fs.is_file(path).await {
-            fs.create_file(path, Default::default()).await?;
-            fs.save(path, &default_content(), Default::default())
-                .await?;
-        }
-
-        let mut items = workspace
-            .update(&mut cx, |workspace, cx| {
-                workspace.with_local_workspace(cx, |workspace, cx| {
-                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
-                })
-            })?
-            .await?
-            .await;
-
-        let item = items.pop().flatten();
-        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
-    })
-}
-
-pub fn join_remote_project(
-    project_id: u64,
-    follow_user_id: u64,
-    app_state: Arc<AppState>,
-    cx: &mut AppContext,
-) -> Task<Result<()>> {
-    let windows = cx.windows();
-    cx.spawn(|mut cx| async move {
-        let existing_workspace = windows.into_iter().find_map(|window| {
-            window.downcast::<Workspace>().and_then(|window| {
-                window
-                    .update(&mut cx, |workspace, cx| {
-                        if workspace.project().read(cx).remote_id() == Some(project_id) {
-                            Some(window)
-                        } else {
-                            None
-                        }
-                    })
-                    .unwrap_or(None)
-            })
-        });
-
-        let workspace = if let Some(existing_workspace) = existing_workspace {
-            existing_workspace
-        } else {
-            let active_call = cx.update(|cx| ActiveCall::global(cx))?;
-            let room = active_call
-                .read_with(&cx, |call, _| call.room().cloned())?
-                .ok_or_else(|| anyhow!("not in a call"))?;
-            let project = room
-                .update(&mut cx, |room, cx| {
-                    room.join_project(
-                        project_id,
-                        app_state.languages.clone(),
-                        app_state.fs.clone(),
-                        cx,
-                    )
-                })?
-                .await?;
-
-            let window_bounds_override = window_bounds_env_override(&cx);
-            cx.update(|cx| {
-                let options = (app_state.build_window_options)(window_bounds_override, None, cx);
-                cx.open_window(options, |cx| {
-                    cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
-                })
-            })?
-        };
-
-        workspace.update(&mut cx, |workspace, cx| {
-            cx.activate(true);
-            cx.activate_window();
-
-            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
-                let follow_peer_id = room
-                    .read(cx)
-                    .remote_participants()
-                    .iter()
-                    .find(|(_, participant)| participant.user.id == follow_user_id)
-                    .map(|(_, p)| p.peer_id)
-                    .or_else(|| {
-                        // If we couldn't follow the given user, follow the host instead.
-                        let collaborator = workspace
-                            .project()
-                            .read(cx)
-                            .collaborators()
-                            .values()
-                            .find(|collaborator| collaborator.replica_id == 0)?;
-                        Some(collaborator.peer_id)
-                    });
-
-                if let Some(follow_peer_id) = follow_peer_id {
-                    workspace.follow(follow_peer_id, cx);
-                }
-            }
-        })?;
-
-        anyhow::Ok(())
-    })
-}
-
-pub fn restart(_: &Restart, cx: &mut AppContext) {
-    let should_confirm = WorkspaceSettings::get_global(cx).confirm_quit;
-    let mut workspace_windows = cx
-        .windows()
-        .into_iter()
-        .filter_map(|window| window.downcast::<Workspace>())
-        .collect::<Vec<_>>();
-
-    // If multiple windows have unsaved changes, and need a save prompt,
-    // prompt in the active window before switching to a different window.
-    workspace_windows.sort_by_key(|window| window.is_active(&cx) == Some(false));
-
-    let mut prompt = None;
-    if let (true, Some(window)) = (should_confirm, workspace_windows.first()) {
-        prompt = window
-            .update(cx, |_, cx| {
-                cx.prompt(
-                    PromptLevel::Info,
-                    "Are you sure you want to restart?",
-                    &["Restart", "Cancel"],
-                )
-            })
-            .ok();
-    }
-
-    cx.spawn(|mut cx| async move {
-        if let Some(prompt) = prompt {
-            let answer = prompt.await?;
-            if answer != 0 {
-                return Ok(());
-            }
-        }
-
-        // If the user cancels any save prompt, then keep the app open.
-        for window in workspace_windows {
-            if let Ok(should_close) = window.update(&mut cx, |workspace, cx| {
-                workspace.prepare_to_close(true, cx)
-            }) {
-                if !should_close.await? {
-                    return Ok(());
-                }
-            }
-        }
-
-        cx.update(|cx| cx.restart())
-    })
-    .detach_and_log_err(cx);
-}
-
-fn parse_pixel_position_env_var(value: &str) -> Option<Point<GlobalPixels>> {
-    let mut parts = value.split(',');
-    let x: usize = parts.next()?.parse().ok()?;
-    let y: usize = parts.next()?.parse().ok()?;
-    Some(point((x as f64).into(), (y as f64).into()))
-}
-
-fn parse_pixel_size_env_var(value: &str) -> Option<Size<GlobalPixels>> {
-    let mut parts = value.split(',');
-    let width: usize = parts.next()?.parse().ok()?;
-    let height: usize = parts.next()?.parse().ok()?;
-    Some(size((width as f64).into(), (height as f64).into()))
-}
-
-struct DisconnectedOverlay;
-
-impl Element for DisconnectedOverlay {
-    type State = AnyElement;
-
-    fn request_layout(
-        &mut self,
-        _: Option<Self::State>,
-        cx: &mut WindowContext,
-    ) -> (LayoutId, Self::State) {
-        let mut background = cx.theme().colors().elevated_surface_background;
-        background.fade_out(0.2);
-        let mut overlay = div()
-            .bg(background)
-            .absolute()
-            .left_0()
-            .top_0()
-            .size_full()
-            .flex()
-            .items_center()
-            .justify_center()
-            .capture_any_mouse_down(|_, cx| cx.stop_propagation())
-            .capture_any_mouse_up(|_, cx| cx.stop_propagation())
-            .child(Label::new(
-                "Your connection to the remote project has been lost.",
-            ))
-            .into_any();
-        (overlay.request_layout(cx), overlay)
-    }
-
-    fn paint(&mut self, bounds: Bounds<Pixels>, overlay: &mut Self::State, cx: &mut WindowContext) {
-        cx.with_z_index(u8::MAX, |cx| {
-            cx.add_opaque_layer(bounds);
-            overlay.paint(cx);
-        })
-    }
-}
-
-impl IntoElement for DisconnectedOverlay {
-    type Element = Self;
-
-    fn element_id(&self) -> Option<ui::prelude::ElementId> {
-        None
-    }
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use std::{cell::RefCell, rc::Rc};
-
-    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;
-
-    #[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.new_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.new_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.new_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.executor());
-        fs.insert_tree(
-            "/root1",
-            json!({
-                "one.txt": "",
-                "two.txt": "",
-            }),
-        )
-        .await;
-        fs.insert_tree(
-            "/root2",
-            json!({
-                "three.txt": "",
-            }),
-        )
-        .await;
-
-        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.update(cx, |project, cx| {
-            project.worktrees().next().unwrap().read(cx).id()
-        });
-
-        let item1 = cx.new_view(|cx| {
-            TestItem::new(cx).with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
-        });
-        let item2 = cx.new_view(|cx| {
-            TestItem::new(cx).with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
-        });
-
-        // Add an item to an empty pane
-        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
-        project.update(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.update(cx, |project, cx| {
-            assert_eq!(
-                project.active_entry(),
-                project
-                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
-                    .map(|e| e.id)
-            );
-        });
-
-        // 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.update(cx, |project, cx| {
-            assert_eq!(
-                project.active_entry(),
-                project
-                    .entry_for_path(&(worktree_id, "one.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!(cx.window_title().as_deref(), Some("one.txt β€” root1, root2"));
-
-        // 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"));
-    }
-
-    #[gpui::test]
-    async fn test_close_window(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree("/root", json!({ "one": "" })).await;
-
-        let project = Project::test(fs, ["root".as_ref()], cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
-
-        // When there are no dirty items, there's nothing to do.
-        let item1 = cx.new_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());
-
-        // When there are dirty untitled items, prompt to save each one. If the user
-        // cancels any prompt, then abort.
-        let item2 = cx.new_view(|cx| TestItem::new(cx).with_dirty(true));
-        let item3 = cx.new_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());
-    }
-
-    #[gpui::test]
-    async fn test_close_pane_items(cx: &mut TestAppContext) {
-        init_test(cx);
-
-        let fs = FakeFs::new(cx.executor());
-
-        let project = Project::test(fs, None, cx).await;
-        let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
-
-        let item1 = cx.new_view(|cx| {
-            TestItem::new(cx)
-                .with_dirty(true)
-                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
-        });
-        let item2 = cx.new_view(|cx| {
-            TestItem::new(cx)
-                .with_dirty(true)
-                .with_conflict(true)
-                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
-        });
-        let item3 = cx.new_view(|cx| {
-            TestItem::new(cx)
-                .with_dirty(true)
-                .with_conflict(true)
-                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
-        });
-        let item4 = cx.new_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()
-        });
-
-        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());
-        });
-    }
-
-    #[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.new_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.new_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.new_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(),
-                ])
-        });
-
-        // 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);
-            });
-
-            let right_pane = workspace
-                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
-                .unwrap();
-
-            right_pane.update(cx, |pane, cx| {
-                pane.add_item(Box::new(item_3_4.clone()), true, true, None, cx);
-            });
-
-            left_pane
-        });
-
-        cx.focus_view(&left_pane);
-
-        // 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.
-
-        let close = left_pane.update(cx, |pane, cx| {
-            pane.close_all_items(&CloseAllItems::default(), cx).unwrap()
-        });
-        cx.executor().run_until_parked();
-
-        // Discard "Save all" prompt
-        cx.simulate_prompt_answer(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);
-
-        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);
-
-        cx.executor().run_until_parked();
-        close.await.unwrap();
-        left_pane.update(cx, |pane, _| {
-            assert_eq!(pane.items_len(), 0);
-        });
-    }
-
-    #[gpui::test]
-    async fn test_autosave(cx: &mut gpui::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));
-        let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
-
-        let item = cx.new_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 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;
-        });
-
-        // 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;
-        });
-
-        // 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));
-
-        // 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();
-
-        cx.executor().run_until_parked();
-        item.update(cx, |item, _| assert_eq!(item.save_count, 3));
-
-        // 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);
-        });
-
-        // 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));
-
-        // 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));
-
-        // 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;
-        });
-
-        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.run_until_parked();
-        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
-
-        // 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.run_until_parked();
-        assert!(cx.has_pending_prompt());
-        item.update(cx, |item, _| assert_eq!(item.save_count, 5));
-    }
-
-    #[gpui::test]
-    async fn test_pane_navigation(cx: &mut gpui::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));
-
-        let item = cx.new_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();
-        });
-
-        pane.update(cx, |pane, _| {
-            assert!(!pane.can_navigate_backward());
-            assert!(!pane.can_navigate_forward());
-        });
-
-        item.update(cx, |item, cx| {
-            item.set_state("one".to_string(), cx);
-        });
-
-        // Toolbar must be notified to re-render the navigation buttons
-        assert_eq!(*toolbar_notify_count.borrow(), 1);
-
-        pane.update(cx, |pane, _| {
-            assert!(pane.can_navigate_backward());
-            assert!(!pane.can_navigate_forward());
-        });
-
-        workspace
-            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
-            .await
-            .unwrap();
-
-        assert_eq!(*toolbar_notify_count.borrow(), 2);
-        pane.update(cx, |pane, _| {
-            assert!(!pane.can_navigate_backward());
-            assert!(pane.can_navigate_forward());
-        });
-    }
-
-    // #[gpui::test]
-    // async fn test_toggle_docks_and_panels(cx: &mut gpui::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));
-
-    //     let panel = workspace.update(cx, |workspace, cx| {
-    //         let panel = cx.build_view(|cx| TestPanel::new(DockPosition::Right, cx));
-    //         workspace.add_panel(panel.clone(), cx);
-
-    //         workspace
-    //             .right_dock()
-    //             .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
-
-    //         panel
-    //     });
-
-    //     let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
-    //     pane.update(cx, |pane, cx| {
-    //         let item = cx.build_view(|cx| TestItem::new(cx));
-    //         pane.add_item(Box::new(item), true, true, None, cx);
-    //     });
-
-    //     // Transfer focus from center to panel
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_panel_focus::<TestPanel>(cx);
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(workspace.right_dock().read(cx).is_open());
-    //         assert!(!panel.is_zoomed(cx));
-    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Transfer focus from panel to center
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_panel_focus::<TestPanel>(cx);
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(workspace.right_dock().read(cx).is_open());
-    //         assert!(!panel.is_zoomed(cx));
-    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Close the dock
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_dock(DockPosition::Right, cx);
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(!workspace.right_dock().read(cx).is_open());
-    //         assert!(!panel.is_zoomed(cx));
-    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Open the dock
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_dock(DockPosition::Right, cx);
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(workspace.right_dock().read(cx).is_open());
-    //         assert!(!panel.is_zoomed(cx));
-    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Focus and zoom panel
-    //     panel.update(cx, |panel, cx| {
-    //         cx.focus_self();
-    //         panel.set_zoomed(true, cx)
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(workspace.right_dock().read(cx).is_open());
-    //         assert!(panel.is_zoomed(cx));
-    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Transfer focus to the center closes the dock
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_panel_focus::<TestPanel>(cx);
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(!workspace.right_dock().read(cx).is_open());
-    //         assert!(panel.is_zoomed(cx));
-    //         assert!(!panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Transferring focus back to the panel keeps it zoomed
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_panel_focus::<TestPanel>(cx);
-    //     });
-
-    //     workspace.update(cx, |workspace, cx| {
-    //         assert!(workspace.right_dock().read(cx).is_open());
-    //         assert!(panel.is_zoomed(cx));
-    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Close the dock while it is zoomed
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_dock(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.read(cx).focus_handle(cx).contains_focused(cx));
-    //     });
-
-    //     // Opening the dock, when it's zoomed, retains focus
-    //     workspace.update(cx, |workspace, cx| {
-    //         workspace.toggle_dock(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_some());
-    //         assert!(panel.read(cx).focus_handle(cx).contains_focused(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));
-
-    //     // 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.focus_handle(cx).is_focused(cx));
-    //         assert!(workspace.right_dock().read(cx).is_open());
-    //         assert!(workspace.zoomed.is_none());
-    //     });
-    // }
-
-    // #[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 (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, 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(|cx| TestPanel::new(DockPosition::Left, cx));
-    //         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(|cx| TestPanel::new(DockPosition::Right, cx));
-    //         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().panel_id(),
-    //             panel_1.panel_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()
-    //                 .panel_id(),
-    //             panel_2.panel_id(),
-    //         );
-
-    //         (panel_1, panel_2)
-    //     });
-
-    //     // 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, 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().panel_id(),
-    //             panel_1.panel_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);
-    //     });
-
-    //     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()
-    //                 .panel_id(),
-    //             panel_1.panel_id(),
-    //         );
-    //     });
-
-    //     // Move panel_1 back to the left
-    //     panel_1.update(cx, |panel_1, cx| {
-    //         panel_1.set_position(DockPosition::Left, cx)
-    //     });
-
-    //     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().panel_id(),
-    //             panel_1.panel_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);
-    //     });
-
-    //     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);
-    //     });
-
-    //     // Emit activated event on panel 1
-    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::Activate));
-
-    //     // 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().panel_id(),
-    //             panel_1.panel_id(),
-    //         );
-    //         assert!(panel_1.focus_handle(cx).is_focused(cx));
-    //     });
-
-    //     // Emit closed event on panel 2, which is not active
-    //     panel_2.update(cx, |_, cx| cx.emit(PanelEvent::Close));
-
-    //     // 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().panel_id(),
-    //             panel_1.panel_id(),
-    //         );
-    //     });
-
-    //     // Emitting a ZoomIn event shows the panel as zoomed.
-    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomIn));
-    //     workspace.update(cx, |workspace, _| {
-    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
-    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
-    //     });
-
-    //     // 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.to_any().downgrade()));
-
-    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-    //     });
-
-    //     // If focus is transferred to another view that's not a panel or another pane, we still show
-    //     // the panel as zoomed.
-    //     let other_focus_handle = cx.update(|cx| cx.focus_handle());
-    //     cx.update(|cx| cx.focus(&other_focus_handle));
-    //     workspace.update(cx, |workspace, _| {
-    //         assert_eq!(workspace.zoomed, Some(panel_1.to_any().downgrade()));
-    //         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);
-    //     });
-
-    //     // 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.
-    //     cx.update(|cx| cx.focus(&other_focus_handle));
-    //     workspace.update(cx, |workspace, _| {
-    //         assert_eq!(workspace.zoomed, None);
-    //         assert_eq!(workspace.zoomed_position, None);
-    //     });
-
-    //     // 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.to_any().downgrade()));
-    //         assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
-    //     });
-
-    //     // Emitting a ZoomOut event unzooms the panel.
-    //     panel_1.update(cx, |_, cx| cx.emit(PanelEvent::ZoomOut));
-    //     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(PanelEvent::Close));
-
-    //     // 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/workspace2/src/workspace_settings.rs πŸ”—

@@ -1,56 +0,0 @@
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use settings::Settings;
-
-#[derive(Deserialize)]
-pub struct WorkspaceSettings {
-    pub active_pane_magnification: f32,
-    pub confirm_quit: bool,
-    pub show_call_status_icon: bool,
-    pub autosave: AutosaveSetting,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct WorkspaceSettingsContent {
-    pub active_pane_magnification: Option<f32>,
-    pub confirm_quit: Option<bool>,
-    pub show_call_status_icon: Option<bool>,
-    pub autosave: Option<AutosaveSetting>,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum AutosaveSetting {
-    Off,
-    AfterDelay { milliseconds: u64 },
-    OnFocusChange,
-    OnWindowChange,
-}
-
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct GitSettings {
-    pub git_gutter: Option<GitGutterSetting>,
-    pub gutter_debounce: Option<u64>,
-}
-
-#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum GitGutterSetting {
-    #[default]
-    TrackedFiles,
-    Hide,
-}
-
-impl Settings for WorkspaceSettings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = WorkspaceSettingsContent;
-
-    fn load(
-        default_value: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        _: &mut gpui::AppContext,
-    ) -> anyhow::Result<Self> {
-        Self::load_via_json_merge(default_value, user_values)
-    }
-}

crates/zed/Cargo.toml πŸ”—

@@ -71,7 +71,7 @@ theme_selector = { path = "../theme_selector" }
 util = { path = "../util" }
 semantic_index = { package = "semantic_index2", path = "../semantic_index2" }
 vim = { path = "../vim" }
-workspace = { package = "workspace2", path = "../workspace2" }
+workspace = { path = "../workspace" }
 welcome = { path = "../welcome" }
 zed_actions = {package = "zed_actions2", path = "../zed_actions2"}
 anyhow.workspace = true