From 72d69d1b140555e39ff781eeadcdbcc336539127 Mon Sep 17 00:00:00 2001 From: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> Date: Thu, 26 Feb 2026 00:37:17 +0100 Subject: [PATCH] multi_workspace: Move up action handler and modal rendering from workspace to multi workspace (#49995) Now that MultiWorkspace is the root view, actions bound to the `Workspace` key context wouldn't be dispatched when `Workspace` is not in the key context stack (e.g. when the sidebar is focused). To fix this, the `Workspace` key context and action handlers are moved up to the MultiWorkspace rendering layer. This avoids introducing a new key context and the keymap migration that would require. This PR also moves modal rendering up a layer so modals are centered within the window (MultiWorkspace element) instead of the Workspace element. ### Before image ### After image Before you mark this PR as ready for review, make sure that you have: - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - N/A --------- Co-authored-by: cameron --- crates/workspace/src/multi_workspace.rs | 11 ++- crates/workspace/src/workspace.rs | 109 +++++++++++++----------- 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/crates/workspace/src/multi_workspace.rs b/crates/workspace/src/multi_workspace.rs index b8651c38b1c6ce54455e0d134acd3777ad285ee4..1c1426ee56f1a4e0220b222ec2a362257c0cd1dd 100644 --- a/crates/workspace/src/multi_workspace.rs +++ b/crates/workspace/src/multi_workspace.rs @@ -460,6 +460,7 @@ impl MultiWorkspace { .update(cx, |workspace, cx| workspace.focus_panel::(window, cx)) } + // used in a test pub fn toggle_modal( &mut self, window: &mut Window, @@ -722,9 +723,12 @@ impl Render for MultiWorkspace { None }; + let workspace = self.workspace().clone(); + let workspace_key_context = workspace.update(cx, |workspace, cx| workspace.key_context(cx)); + let root = workspace.update(cx, |workspace, cx| workspace.actions(h_flex(), window, cx)); + client_side_decorations( - h_flex() - .key_context("Workspace") + root.key_context(workspace_key_context) .relative() .size_full() .on_action(cx.listener(Self::close_window)) @@ -777,7 +781,8 @@ impl Render for MultiWorkspace { .overflow_hidden() .when(is_zoomed, |this| this.absolute().inset_0()) .child(self.workspace().clone()), - ), + ) + .child(self.workspace().read(cx).modal_layer.clone()), window, cx, Tiling { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index f680007924f5061756a864f8c4330345a69403f4..bcc6f2ccc26c967537e5c9069ae3c8da7e0a1402 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1251,7 +1251,7 @@ pub struct Workspace { last_active_center_pane: Option>, last_active_view_id: Option, status_bar: Entity, - modal_layer: Entity, + pub(crate) modal_layer: Entity, toast_layer: Entity, titlebar_item: Option, notifications: Notifications, @@ -6333,7 +6333,47 @@ impl Workspace { }) } - fn actions(&self, div: Div, window: &mut Window, cx: &mut Context) -> Div { + pub fn key_context(&self, cx: &App) -> KeyContext { + let mut context = KeyContext::new_with_defaults(); + context.add("Workspace"); + context.set("keyboard_layout", cx.keyboard_layout().name().to_string()); + if let Some(status) = self + .debugger_provider + .as_ref() + .and_then(|provider| provider.active_thread_state(cx)) + { + match status { + ThreadStatus::Running | ThreadStatus::Stepping => { + context.add("debugger_running"); + } + ThreadStatus::Stopped => context.add("debugger_stopped"), + ThreadStatus::Exited | ThreadStatus::Ended => {} + } + } + + if self.left_dock.read(cx).is_open() { + if let Some(active_panel) = self.left_dock.read(cx).active_panel() { + context.set("left_dock", active_panel.panel_key()); + } + } + + if self.right_dock.read(cx).is_open() { + if let Some(active_panel) = self.right_dock.read(cx).active_panel() { + context.set("right_dock", active_panel.panel_key()); + } + } + + if self.bottom_dock.read(cx).is_open() { + if let Some(active_panel) = self.bottom_dock.read(cx).active_panel() { + context.set("bottom_dock", active_panel.panel_key()); + } + } + + context + } + + /// Multiworkspace uses this to add workspace action handling to itself + pub fn actions(&self, div: Div, window: &mut Window, cx: &mut Context) -> Div { self.add_workspace_actions_listeners(div, window, cx) .on_action(cx.listener( |_workspace, action_sequence: &settings::ActionSequence, window, cx| { @@ -7390,40 +7430,6 @@ impl Render for Workspace { if FIRST_PAINT.swap(false, std::sync::atomic::Ordering::Relaxed) { log::info!("Rendered first frame"); } - let mut context = KeyContext::new_with_defaults(); - context.add("Workspace"); - context.set("keyboard_layout", cx.keyboard_layout().name().to_string()); - if let Some(status) = self - .debugger_provider - .as_ref() - .and_then(|provider| provider.active_thread_state(cx)) - { - match status { - ThreadStatus::Running | ThreadStatus::Stepping => { - context.add("debugger_running"); - } - ThreadStatus::Stopped => context.add("debugger_stopped"), - ThreadStatus::Exited | ThreadStatus::Ended => {} - } - } - - if self.left_dock.read(cx).is_open() { - if let Some(active_panel) = self.left_dock.read(cx).active_panel() { - context.set("left_dock", active_panel.panel_key()); - } - } - - if self.right_dock.read(cx).is_open() { - if let Some(active_panel) = self.right_dock.read(cx).active_panel() { - context.set("right_dock", active_panel.panel_key()); - } - } - - if self.bottom_dock.read(cx).is_open() { - if let Some(active_panel) = self.bottom_dock.read(cx).active_panel() { - context.set("bottom_dock", active_panel.panel_key()); - } - } let centered_layout = self.centered_layout && self.center.panes().len() == 1 @@ -7461,8 +7467,7 @@ impl Render for Workspace { .collect::>(); let bottom_dock_layout = WorkspaceSettings::get_global(cx).bottom_dock_layout; - self.actions(div(), window, cx) - .key_context(context) + div() .relative() .size_full() .flex() @@ -7862,7 +7867,6 @@ impl Render for Workspace { .when(self.status_bar_visible(cx), |parent| { parent.child(self.status_bar.clone()) }) - .child(self.modal_layer.clone()) .child(self.toast_layer.clone()), ) } @@ -10057,6 +10061,7 @@ mod tests { let project_b = Project::test(fs, ["root".as_ref()], cx).await; let multi_workspace_handle = cx.add_window(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); + cx.run_until_parked(); let workspace_a = multi_workspace_handle .read_with(cx, |mw, _| mw.workspace().clone()) @@ -10636,8 +10641,9 @@ mod tests { init_test(cx); let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; - let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); + let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); workspace.update_in(cx, |workspace, window, cx| { let first_item = cx.new(|cx| { @@ -11131,8 +11137,9 @@ mod tests { init_test(cx); let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; - let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); + let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); // Open two docks (left and right) with one panel each let (left_panel, right_panel) = workspace.update_in(cx, |workspace, window, cx| { @@ -11563,8 +11570,9 @@ mod tests { let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; - let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); + let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); let (panel_1, panel_2) = workspace.update_in(cx, |workspace, window, cx| { let panel_1 = cx.new(|cx| TestPanel::new(DockPosition::Left, 100, cx)); @@ -12471,8 +12479,9 @@ mod tests { init_test(cx); let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; - let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); + let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); // Add a new panel to the right dock, opening the dock and setting the // focus to the new panel. @@ -13161,8 +13170,9 @@ mod tests { let fs = FakeFs::new(cx.executor()); let project = Project::test(fs, [], cx).await; - let (workspace, cx) = - cx.add_window_view(|window, cx| Workspace::test_new(project, window, cx)); + let (multi_workspace, cx) = + cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx)); + let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone()); let panel = workspace.update_in(cx, |workspace, window, cx| { let panel = cx.new(|cx| TestPanel::new(DockPosition::Right, 100, cx)); workspace.add_panel(panel.clone(), window, cx); @@ -13226,6 +13236,7 @@ mod tests { let multi_workspace_handle = cx.add_window(|window, cx| MultiWorkspace::test_new(project_a.clone(), window, cx)); + cx.run_until_parked(); let workspace_a = multi_workspace_handle .read_with(cx, |mw, _| mw.workspace().clone())