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
### After
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())