diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index dd11a3f2cc..a0f117b0bf 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -576,6 +576,10 @@ impl Item for AgentDiffPane { }); } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 18847363bf..5db588fdb3 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -493,6 +493,10 @@ impl Item for ChannelView { None } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _: Option, diff --git a/crates/diagnostics/src/buffer_diagnostics.rs b/crates/diagnostics/src/buffer_diagnostics.rs index 1a7a97c686..1205cef385 100644 --- a/crates/diagnostics/src/buffer_diagnostics.rs +++ b/crates/diagnostics/src/buffer_diagnostics.rs @@ -688,6 +688,10 @@ impl Item for BufferDiagnosticsEditor { true } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index b96e8f891f..5a43fd1353 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -727,6 +727,10 @@ impl Item for ProjectDiagnosticsEditor { }); } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 6dab57db52..346574eba4 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -757,6 +757,10 @@ impl Item for Editor { self.buffer.read(cx).is_singleton() } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 00e6321fdb..bf3732b7e8 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -69,6 +69,7 @@ pub struct WasmExtension { pub work_dir: Arc, #[allow(unused)] pub zed_api_version: SemanticVersion, + _task: Arc>>, } impl Drop for WasmExtension { @@ -649,21 +650,26 @@ impl WasmHost { anyhow::Ok(( extension_task, - WasmExtension { - manifest: manifest.clone(), - work_dir: this.work_dir.join(manifest.id.as_ref()).into(), - tx, - zed_api_version, - }, + manifest.clone(), + this.work_dir.join(manifest.id.as_ref()).into(), + tx, + zed_api_version, )) }; cx.spawn(async move |cx| { - let (extension_task, extension) = load_extension_task.await?; + let (extension_task, manifest, work_dir, tx, zed_api_version) = + load_extension_task.await?; // we need to run run the task in an extension context as wasmtime_wasi may // call into tokio, accessing its runtime handle - gpui_tokio::Tokio::spawn(cx, extension_task)?.detach(); + let task = Arc::new(gpui_tokio::Tokio::spawn(cx, extension_task)?); - Ok(extension) + Ok(WasmExtension { + manifest, + work_dir, + tx, + zed_api_version, + _task: task, + }) }) } diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 2430796c89..0a0c4c18e1 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -556,6 +556,10 @@ impl Item for CommitView { }); } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index e6f8099c73..5c49ca286e 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -709,6 +709,10 @@ impl Item for ProjectDiff { }); } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/gpui_tokio/src/gpui_tokio.rs b/crates/gpui_tokio/src/gpui_tokio.rs index 8384f2a88e..61dcfc48ef 100644 --- a/crates/gpui_tokio/src/gpui_tokio.rs +++ b/crates/gpui_tokio/src/gpui_tokio.rs @@ -1,9 +1,10 @@ use std::future::Future; use gpui::{App, AppContext, Global, ReadGlobal, Task}; -use tokio::task::JoinError; use util::defer; +pub use tokio::task::JoinError; + pub fn init(cx: &mut App) { cx.set_global(GlobalTokio::new()); } diff --git a/crates/image_viewer/src/image_viewer.rs b/crates/image_viewer/src/image_viewer.rs index 17259d15f1..f9a2cc9e04 100644 --- a/crates/image_viewer/src/image_viewer.rs +++ b/crates/image_viewer/src/image_viewer.rs @@ -174,6 +174,10 @@ impl Item for ImageView { }]) } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/language_tools/src/key_context_view.rs b/crates/language_tools/src/key_context_view.rs index e704d6bbf0..cc34838010 100644 --- a/crates/language_tools/src/key_context_view.rs +++ b/crates/language_tools/src/key_context_view.rs @@ -153,6 +153,10 @@ impl Item for KeyContextView { None } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/language_tools/src/lsp_log_view.rs b/crates/language_tools/src/lsp_log_view.rs index b1e24303c4..d480eadc73 100644 --- a/crates/language_tools/src/lsp_log_view.rs +++ b/crates/language_tools/src/lsp_log_view.rs @@ -758,6 +758,10 @@ impl Item for LspLogView { } } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index 9e5a0374f5..e2a0cd4c33 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -568,6 +568,10 @@ impl Item for SyntaxTreeView { None } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _: Option, diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index 913d92d48c..562dea8748 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -378,6 +378,10 @@ impl Item for Onboarding { false } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/repl/src/notebook/notebook_ui.rs b/crates/repl/src/notebook/notebook_ui.rs index 209948685c..eaeff234bc 100644 --- a/crates/repl/src/notebook/notebook_ui.rs +++ b/crates/repl/src/notebook/notebook_ui.rs @@ -694,6 +694,10 @@ impl EventEmitter<()> for NotebookEditor {} impl Item for NotebookEditor { type Event = (); + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 3a9367db72..f407a0a4db 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -567,6 +567,10 @@ impl Item for ProjectSearchView { .update(cx, |editor, cx| editor.reload(project, window, cx)) } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 5111f3a99b..ff169e48e5 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -1213,6 +1213,10 @@ impl Item for TerminalView { workspace::item::ItemBufferKind::Singleton } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, workspace_id: Option, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a328bb6792..b77075f92b 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -213,16 +213,21 @@ pub trait Item: Focusable + EventEmitter + Render + Sized { ItemBufferKind::None } fn set_nav_history(&mut self, _: ItemNavHistory, _window: &mut Window, _: &mut Context) {} + + fn can_split(&self) -> bool { + false + } fn clone_on_split( &self, - _workspace_id: Option, - _window: &mut Window, - _: &mut Context, + workspace_id: Option, + window: &mut Window, + cx: &mut Context, ) -> Task>> where Self: Sized, { - Task::ready(None) + _ = (workspace_id, window, cx); + unimplemented!("clone_on_split() must be implemented if can_split() returns true") } fn is_dirty(&self, _: &App) -> bool { false @@ -418,6 +423,7 @@ pub trait ItemHandle: 'static + Send { ); fn buffer_kind(&self, cx: &App) -> ItemBufferKind; fn boxed_clone(&self) -> Box; + fn can_split(&self, cx: &App) -> bool; fn clone_on_split( &self, workspace_id: Option, @@ -631,6 +637,10 @@ impl ItemHandle for Entity { Box::new(self.clone()) } + fn can_split(&self, cx: &App) -> bool { + self.read(cx).can_split() + } + fn clone_on_split( &self, workspace_id: Option, @@ -1503,6 +1513,10 @@ pub mod test { self.push_to_nav_history(cx); } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 178fbdff9f..9b6767086a 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -3292,18 +3292,22 @@ impl Pane { else { return; }; - let task = item.clone_on_split(database_id, window, cx); - let to_pane = to_pane.downgrade(); - cx.spawn_in(window, async move |_, cx| { - if let Some(item) = task.await { - to_pane - .update_in(cx, |pane, window, cx| { - pane.add_item(item, true, true, None, window, cx) - }) - .ok(); - } - }) - .detach(); + if item.can_split(cx) { + let task = item.clone_on_split(database_id, window, cx); + let to_pane = to_pane.downgrade(); + cx.spawn_in(window, async move |_, cx| { + if let Some(item) = task.await { + to_pane + .update_in(cx, |pane, window, cx| { + pane.add_item(item, true, true, None, window, cx) + }) + .ok(); + } + }) + .detach(); + } else { + move_item(&from_pane, &to_pane, item_id, ix, true, window, cx); + } } else { move_item(&from_pane, &to_pane, item_id, ix, true, window, cx); } @@ -3597,6 +3601,11 @@ fn default_render_tab_bar_buttons( if !pane.has_focus(window, cx) && !pane.context_menu_focused(window, cx) { return (None, None); } + let (can_clone, can_split_move) = match pane.active_item() { + Some(active_item) if active_item.can_split(cx) => (true, false), + Some(_) => (false, pane.items_len() > 1), + None => (false, false), + }; // Ideally we would return a vec of elements here to pass directly to the [TabBar]'s // `end_slot`, but due to needing a view here that isn't possible. let right_children = h_flex() @@ -3633,17 +3642,26 @@ fn default_render_tab_bar_buttons( .child( PopoverMenu::new("pane-tab-bar-split") .trigger_with_tooltip( - IconButton::new("split", IconName::Split).icon_size(IconSize::Small), + IconButton::new("split", IconName::Split) + .icon_size(IconSize::Small) + .disabled(!can_clone && !can_split_move), Tooltip::text("Split Pane"), ) .anchor(Corner::TopRight) .with_handle(pane.split_item_context_menu_handle.clone()) .menu(move |window, cx| { ContextMenu::build(window, 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()) + if can_split_move { + menu.action("Split Right", SplitAndMoveRight.boxed_clone()) + .action("Split Left", SplitAndMoveLeft.boxed_clone()) + .action("Split Up", SplitAndMoveUp.boxed_clone()) + .action("Split Down", SplitAndMoveDown.boxed_clone()) + } else { + 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()) + } }) .into() }), diff --git a/crates/workspace/src/shared_screen.rs b/crates/workspace/src/shared_screen.rs index 34c7d27df7..3c009f613e 100644 --- a/crates/workspace/src/shared_screen.rs +++ b/crates/workspace/src/shared_screen.rs @@ -109,6 +109,10 @@ impl Item for SharedScreen { self.nav_history = Some(history); } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/workspace/src/theme_preview.rs b/crates/workspace/src/theme_preview.rs index 29067400bd..94a280b4da 100644 --- a/crates/workspace/src/theme_preview.rs +++ b/crates/workspace/src/theme_preview.rs @@ -97,6 +97,10 @@ impl Item for ThemePreview { None } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 74c98d6818..6933a6bcda 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3663,24 +3663,31 @@ impl Workspace { }; if action.clone { - clone_active_item( - self.database_id(), - &self.active_pane, - &destination, - action.focus, - window, - cx, - ) - } else { - move_active_item( - &self.active_pane, - &destination, - action.focus, - true, - window, - cx, - ) + if self + .active_pane + .read(cx) + .active_item() + .is_some_and(|item| item.can_split(cx)) + { + clone_active_item( + self.database_id(), + &self.active_pane, + &destination, + action.focus, + window, + cx, + ); + return; + } } + move_active_item( + &self.active_pane, + &destination, + action.focus, + true, + window, + cx, + ) } pub fn activate_next_pane(&mut self, window: &mut Window, cx: &mut App) { @@ -3841,24 +3848,31 @@ impl Workspace { }; if action.clone { - clone_active_item( - self.database_id(), - &self.active_pane, - &destination, - action.focus, - window, - cx, - ) - } else { - move_active_item( - &self.active_pane, - &destination, - action.focus, - true, - window, - cx, - ); + if self + .active_pane + .read(cx) + .active_item() + .is_some_and(|item| item.can_split(cx)) + { + clone_active_item( + self.database_id(), + &self.active_pane, + &destination, + action.focus, + window, + cx, + ); + return; + } } + move_active_item( + &self.active_pane, + &destination, + action.focus, + true, + window, + cx, + ); } pub fn bounding_box_for_pane(&self, pane: &Entity) -> Option> { @@ -4141,6 +4155,9 @@ impl Workspace { let Some(item) = pane.read(cx).active_item() else { return Task::ready(None); }; + if !item.can_split(cx) { + return Task::ready(None); + } let task = item.clone_on_split(self.database_id(), window, cx); cx.spawn_in(window, async move |this, cx| { if let Some(clone) = task.await { @@ -8225,6 +8242,9 @@ pub fn clone_active_item( let Some(active_item) = source.read(cx).active_item() else { return; }; + if !active_item.can_split(cx) { + return; + } let destination = destination.downgrade(); let task = active_item.clone_on_split(workspace_id, window, cx); window diff --git a/crates/zed/src/zed/component_preview.rs b/crates/zed/src/zed/component_preview.rs index 153d66f04e..d62f39ef63 100644 --- a/crates/zed/src/zed/component_preview.rs +++ b/crates/zed/src/zed/component_preview.rs @@ -715,6 +715,10 @@ impl Item for ComponentPreview { false } + fn can_split(&self) -> bool { + true + } + fn clone_on_split( &self, _workspace_id: Option,