multi_workspace.rs

  1use anyhow::Result;
  2use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
  3use gpui::{
  4    AnyView, App, Context, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
  5    ManagedView, MouseButton, Pixels, Render, Subscription, Task, Tiling, Window, WindowId,
  6    actions, deferred, px,
  7};
  8use project::Project;
  9use std::future::Future;
 10use std::path::PathBuf;
 11use ui::prelude::*;
 12use util::ResultExt;
 13
 14const SIDEBAR_RESIZE_HANDLE_SIZE: Pixels = px(6.0);
 15
 16use crate::{
 17    CloseIntent, CloseWindow, DockPosition, Event as WorkspaceEvent, Item, ModalView, Panel, Toast,
 18    Workspace, WorkspaceId, client_side_decorations, notifications::NotificationId,
 19};
 20
 21actions!(
 22    multi_workspace,
 23    [
 24        /// Creates a new workspace within the current window.
 25        NewWorkspaceInWindow,
 26        /// Switches to the next workspace within the current window.
 27        NextWorkspaceInWindow,
 28        /// Switches to the previous workspace within the current window.
 29        PreviousWorkspaceInWindow,
 30        /// Toggles the workspace switcher sidebar.
 31        ToggleWorkspaceSidebar,
 32        /// Moves focus to or from the workspace sidebar without closing it.
 33        FocusWorkspaceSidebar,
 34    ]
 35);
 36
 37pub enum SidebarEvent {
 38    Open,
 39    Close,
 40}
 41
 42pub trait Sidebar: EventEmitter<SidebarEvent> + Focusable + Render + Sized {
 43    fn width(&self, cx: &App) -> Pixels;
 44    fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
 45    fn has_notifications(&self, cx: &App) -> bool;
 46}
 47
 48pub trait SidebarHandle: 'static + Send + Sync {
 49    fn width(&self, cx: &App) -> Pixels;
 50    fn set_width(&self, width: Option<Pixels>, cx: &mut App);
 51    fn focus_handle(&self, cx: &App) -> FocusHandle;
 52    fn focus(&self, window: &mut Window, cx: &mut App);
 53    fn has_notifications(&self, cx: &App) -> bool;
 54    fn to_any(&self) -> AnyView;
 55    fn entity_id(&self) -> EntityId;
 56}
 57
 58#[derive(Clone)]
 59pub struct DraggedSidebar;
 60
 61impl Render for DraggedSidebar {
 62    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 63        gpui::Empty
 64    }
 65}
 66
 67impl<T: Sidebar> SidebarHandle for Entity<T> {
 68    fn width(&self, cx: &App) -> Pixels {
 69        self.read(cx).width(cx)
 70    }
 71
 72    fn set_width(&self, width: Option<Pixels>, cx: &mut App) {
 73        self.update(cx, |this, cx| this.set_width(width, cx))
 74    }
 75
 76    fn focus_handle(&self, cx: &App) -> FocusHandle {
 77        self.read(cx).focus_handle(cx)
 78    }
 79
 80    fn focus(&self, window: &mut Window, cx: &mut App) {
 81        let handle = self.read(cx).focus_handle(cx);
 82        window.focus(&handle, cx);
 83    }
 84
 85    fn has_notifications(&self, cx: &App) -> bool {
 86        self.read(cx).has_notifications(cx)
 87    }
 88
 89    fn to_any(&self) -> AnyView {
 90        self.clone().into()
 91    }
 92
 93    fn entity_id(&self) -> EntityId {
 94        Entity::entity_id(self)
 95    }
 96}
 97
 98pub struct MultiWorkspace {
 99    window_id: WindowId,
100    workspaces: Vec<Entity<Workspace>>,
101    active_workspace_index: usize,
102    sidebar: Option<Box<dyn SidebarHandle>>,
103    sidebar_open: bool,
104    _sidebar_subscription: Option<Subscription>,
105    pending_removal_tasks: Vec<Task<()>>,
106    _serialize_task: Option<Task<()>>,
107    _create_task: Option<Task<()>>,
108    _subscriptions: Vec<Subscription>,
109}
110
111impl MultiWorkspace {
112    pub fn new(workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
113        let release_subscription = cx.on_release(|this: &mut MultiWorkspace, _cx| {
114            if let Some(task) = this._serialize_task.take() {
115                task.detach();
116            }
117            if let Some(task) = this._create_task.take() {
118                task.detach();
119            }
120            for task in std::mem::take(&mut this.pending_removal_tasks) {
121                task.detach();
122            }
123        });
124        let quit_subscription = cx.on_app_quit(Self::app_will_quit);
125        Self::subscribe_to_workspace(&workspace, cx);
126        Self {
127            window_id: window.window_handle().window_id(),
128            workspaces: vec![workspace],
129            active_workspace_index: 0,
130            sidebar: None,
131            sidebar_open: false,
132            _sidebar_subscription: None,
133            pending_removal_tasks: Vec::new(),
134            _serialize_task: None,
135            _create_task: None,
136            _subscriptions: vec![release_subscription, quit_subscription],
137        }
138    }
139
140    pub fn register_sidebar<T: Sidebar>(
141        &mut self,
142        sidebar: Entity<T>,
143        window: &mut Window,
144        cx: &mut Context<Self>,
145    ) {
146        let subscription =
147            cx.subscribe_in(&sidebar, window, |this, _, event, window, cx| match event {
148                SidebarEvent::Open => this.toggle_sidebar(window, cx),
149                SidebarEvent::Close => {
150                    this.close_sidebar(window, cx);
151                }
152            });
153        self.sidebar = Some(Box::new(sidebar));
154        self._sidebar_subscription = Some(subscription);
155    }
156
157    pub fn sidebar(&self) -> Option<&dyn SidebarHandle> {
158        self.sidebar.as_deref()
159    }
160
161    pub fn sidebar_open(&self) -> bool {
162        self.sidebar_open && self.sidebar.is_some()
163    }
164
165    pub fn sidebar_has_notifications(&self, cx: &App) -> bool {
166        self.sidebar
167            .as_ref()
168            .map_or(false, |s| s.has_notifications(cx))
169    }
170
171    pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
172        cx.has_flag::<AgentV2FeatureFlag>()
173    }
174
175    pub fn toggle_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
176        if !self.multi_workspace_enabled(cx) {
177            return;
178        }
179
180        if self.sidebar_open {
181            self.close_sidebar(window, cx);
182        } else {
183            self.open_sidebar(cx);
184            if let Some(sidebar) = &self.sidebar {
185                sidebar.focus(window, cx);
186            }
187        }
188    }
189
190    pub fn focus_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
191        if !self.multi_workspace_enabled(cx) {
192            return;
193        }
194
195        if self.sidebar_open {
196            let sidebar_is_focused = self
197                .sidebar
198                .as_ref()
199                .is_some_and(|s| s.focus_handle(cx).contains_focused(window, cx));
200
201            if sidebar_is_focused {
202                let pane = self.workspace().read(cx).active_pane().clone();
203                let pane_focus = pane.read(cx).focus_handle(cx);
204                window.focus(&pane_focus, cx);
205            } else if let Some(sidebar) = &self.sidebar {
206                sidebar.focus(window, cx);
207            }
208        } else {
209            self.open_sidebar(cx);
210            if let Some(sidebar) = &self.sidebar {
211                sidebar.focus(window, cx);
212            }
213        }
214    }
215
216    pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
217        self.sidebar_open = true;
218        for workspace in &self.workspaces {
219            workspace.update(cx, |workspace, cx| {
220                workspace.set_workspace_sidebar_open(true, cx);
221            });
222        }
223        self.serialize(cx);
224        cx.notify();
225    }
226
227    fn close_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
228        self.sidebar_open = false;
229        for workspace in &self.workspaces {
230            workspace.update(cx, |workspace, cx| {
231                workspace.set_workspace_sidebar_open(false, cx);
232            });
233        }
234        let pane = self.workspace().read(cx).active_pane().clone();
235        let pane_focus = pane.read(cx).focus_handle(cx);
236        window.focus(&pane_focus, cx);
237        self.serialize(cx);
238        cx.notify();
239    }
240
241    pub fn close_window(&mut self, _: &CloseWindow, window: &mut Window, cx: &mut Context<Self>) {
242        cx.spawn_in(window, async move |this, cx| {
243            let workspaces = this.update(cx, |multi_workspace, _cx| {
244                multi_workspace.workspaces().to_vec()
245            })?;
246
247            for workspace in workspaces {
248                let should_continue = workspace
249                    .update_in(cx, |workspace, window, cx| {
250                        workspace.prepare_to_close(CloseIntent::CloseWindow, window, cx)
251                    })?
252                    .await?;
253                if !should_continue {
254                    return anyhow::Ok(());
255                }
256            }
257
258            cx.update(|window, _cx| {
259                window.remove_window();
260            })?;
261
262            anyhow::Ok(())
263        })
264        .detach_and_log_err(cx);
265    }
266
267    fn subscribe_to_workspace(workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
268        cx.subscribe(workspace, |this, workspace, event, cx| {
269            if let WorkspaceEvent::Activate = event {
270                this.activate(workspace, cx);
271            }
272        })
273        .detach();
274    }
275
276    pub fn is_sidebar_open(&self) -> bool {
277        self.sidebar_open
278    }
279
280    pub fn workspace(&self) -> &Entity<Workspace> {
281        &self.workspaces[self.active_workspace_index]
282    }
283
284    pub fn workspaces(&self) -> &[Entity<Workspace>] {
285        &self.workspaces
286    }
287
288    pub fn active_workspace_index(&self) -> usize {
289        self.active_workspace_index
290    }
291
292    pub fn activate(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) {
293        if !self.multi_workspace_enabled(cx) {
294            self.workspaces[0] = workspace;
295            self.active_workspace_index = 0;
296            cx.notify();
297            return;
298        }
299
300        let old_index = self.active_workspace_index;
301        let new_index = self.set_active_workspace(workspace, cx);
302        if old_index != new_index {
303            self.serialize(cx);
304        }
305    }
306
307    fn set_active_workspace(
308        &mut self,
309        workspace: Entity<Workspace>,
310        cx: &mut Context<Self>,
311    ) -> usize {
312        let index = self.add_workspace(workspace, cx);
313        self.active_workspace_index = index;
314        cx.notify();
315        index
316    }
317
318    /// Adds a workspace to this window without changing which workspace is active.
319    /// Returns the index of the workspace (existing or newly inserted).
320    pub fn add_workspace(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) -> usize {
321        if let Some(index) = self.workspaces.iter().position(|w| *w == workspace) {
322            index
323        } else {
324            if self.sidebar_open {
325                workspace.update(cx, |workspace, cx| {
326                    workspace.set_workspace_sidebar_open(true, cx);
327                });
328            }
329            Self::subscribe_to_workspace(&workspace, cx);
330            self.workspaces.push(workspace);
331            cx.notify();
332            self.workspaces.len() - 1
333        }
334    }
335
336    pub fn activate_index(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
337        debug_assert!(
338            index < self.workspaces.len(),
339            "workspace index out of bounds"
340        );
341        self.active_workspace_index = index;
342        self.serialize(cx);
343        self.focus_active_workspace(window, cx);
344        cx.notify();
345    }
346
347    pub fn activate_next_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
348        if self.workspaces.len() > 1 {
349            let next_index = (self.active_workspace_index + 1) % self.workspaces.len();
350            self.activate_index(next_index, window, cx);
351        }
352    }
353
354    pub fn activate_previous_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
355        if self.workspaces.len() > 1 {
356            let prev_index = if self.active_workspace_index == 0 {
357                self.workspaces.len() - 1
358            } else {
359                self.active_workspace_index - 1
360            };
361            self.activate_index(prev_index, window, cx);
362        }
363    }
364
365    fn serialize(&mut self, cx: &mut App) {
366        let window_id = self.window_id;
367        let state = crate::persistence::model::MultiWorkspaceState {
368            active_workspace_id: self.workspace().read(cx).database_id(),
369            sidebar_open: self.sidebar_open,
370        };
371        self._serialize_task = Some(cx.background_spawn(async move {
372            crate::persistence::write_multi_workspace_state(window_id, state).await;
373        }));
374    }
375
376    /// Returns the in-flight serialization task (if any) so the caller can
377    /// await it. Used by the quit handler to ensure pending DB writes
378    /// complete before the process exits.
379    pub fn flush_serialization(&mut self) -> Task<()> {
380        self._serialize_task.take().unwrap_or(Task::ready(()))
381    }
382
383    fn app_will_quit(&mut self, _cx: &mut Context<Self>) -> impl Future<Output = ()> + use<> {
384        let mut tasks: Vec<Task<()>> = Vec::new();
385        if let Some(task) = self._serialize_task.take() {
386            tasks.push(task);
387        }
388        if let Some(task) = self._create_task.take() {
389            tasks.push(task);
390        }
391        tasks.extend(std::mem::take(&mut self.pending_removal_tasks));
392
393        async move {
394            futures::future::join_all(tasks).await;
395        }
396    }
397
398    fn focus_active_workspace(&self, window: &mut Window, cx: &mut App) {
399        // If a dock panel is zoomed, focus it instead of the center pane.
400        // Otherwise, focusing the center pane triggers dismiss_zoomed_items_to_reveal
401        // which closes the zoomed dock.
402        let focus_handle = {
403            let workspace = self.workspace().read(cx);
404            let mut target = None;
405            for dock in workspace.all_docks() {
406                let dock = dock.read(cx);
407                if dock.is_open() {
408                    if let Some(panel) = dock.active_panel() {
409                        if panel.is_zoomed(window, cx) {
410                            target = Some(panel.panel_focus_handle(cx));
411                            break;
412                        }
413                    }
414                }
415            }
416            target.unwrap_or_else(|| {
417                let pane = workspace.active_pane().clone();
418                pane.read(cx).focus_handle(cx)
419            })
420        };
421        window.focus(&focus_handle, cx);
422    }
423
424    pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
425        self.workspace().read(cx).panel::<T>(cx)
426    }
427
428    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
429        self.workspace().read(cx).active_modal::<V>(cx)
430    }
431
432    pub fn add_panel<T: Panel>(
433        &mut self,
434        panel: Entity<T>,
435        window: &mut Window,
436        cx: &mut Context<Self>,
437    ) {
438        self.workspace().update(cx, |workspace, cx| {
439            workspace.add_panel(panel, window, cx);
440        });
441    }
442
443    pub fn focus_panel<T: Panel>(
444        &mut self,
445        window: &mut Window,
446        cx: &mut Context<Self>,
447    ) -> Option<Entity<T>> {
448        self.workspace()
449            .update(cx, |workspace, cx| workspace.focus_panel::<T>(window, cx))
450    }
451
452    pub fn toggle_modal<V: ModalView, B>(
453        &mut self,
454        window: &mut Window,
455        cx: &mut Context<Self>,
456        build: B,
457    ) where
458        B: FnOnce(&mut Window, &mut gpui::Context<V>) -> V,
459    {
460        self.workspace().update(cx, |workspace, cx| {
461            workspace.toggle_modal(window, cx, build);
462        });
463    }
464
465    pub fn toggle_dock(
466        &mut self,
467        dock_side: DockPosition,
468        window: &mut Window,
469        cx: &mut Context<Self>,
470    ) {
471        self.workspace().update(cx, |workspace, cx| {
472            workspace.toggle_dock(dock_side, window, cx);
473        });
474    }
475
476    pub fn active_item_as<I: 'static>(&self, cx: &App) -> Option<Entity<I>> {
477        self.workspace().read(cx).active_item_as::<I>(cx)
478    }
479
480    pub fn items_of_type<'a, T: Item>(
481        &'a self,
482        cx: &'a App,
483    ) -> impl 'a + Iterator<Item = Entity<T>> {
484        self.workspace().read(cx).items_of_type::<T>(cx)
485    }
486
487    pub fn database_id(&self, cx: &App) -> Option<WorkspaceId> {
488        self.workspace().read(cx).database_id()
489    }
490
491    pub fn take_pending_removal_tasks(&mut self) -> Vec<Task<()>> {
492        let mut tasks: Vec<Task<()>> = std::mem::take(&mut self.pending_removal_tasks)
493            .into_iter()
494            .filter(|task| !task.is_ready())
495            .collect();
496        if let Some(task) = self._create_task.take() {
497            if !task.is_ready() {
498                tasks.push(task);
499            }
500        }
501        tasks
502    }
503
504    #[cfg(any(test, feature = "test-support"))]
505    pub fn set_random_database_id(&mut self, cx: &mut Context<Self>) {
506        self.workspace().update(cx, |workspace, _cx| {
507            workspace.set_random_database_id();
508        });
509    }
510
511    #[cfg(any(test, feature = "test-support"))]
512    pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
513        let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
514        Self::new(workspace, window, cx)
515    }
516
517    #[cfg(any(test, feature = "test-support"))]
518    pub fn test_add_workspace(
519        &mut self,
520        project: Entity<Project>,
521        window: &mut Window,
522        cx: &mut Context<Self>,
523    ) -> Entity<Workspace> {
524        let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
525        self.activate(workspace.clone(), cx);
526        workspace
527    }
528
529    pub fn create_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
530        if !self.multi_workspace_enabled(cx) {
531            return;
532        }
533        let app_state = self.workspace().read(cx).app_state().clone();
534        let project = Project::local(
535            app_state.client.clone(),
536            app_state.node_runtime.clone(),
537            app_state.user_store.clone(),
538            app_state.languages.clone(),
539            app_state.fs.clone(),
540            None,
541            project::LocalProjectFlags::default(),
542            cx,
543        );
544        let new_workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
545        self.set_active_workspace(new_workspace.clone(), cx);
546        self.focus_active_workspace(window, cx);
547
548        let weak_workspace = new_workspace.downgrade();
549        self._create_task = Some(cx.spawn_in(window, async move |this, cx| {
550            let result = crate::persistence::DB.next_id().await;
551            this.update_in(cx, |this, window, cx| match result {
552                Ok(workspace_id) => {
553                    if let Some(workspace) = weak_workspace.upgrade() {
554                        let session_id = workspace.read(cx).session_id();
555                        let window_id = window.window_handle().window_id().as_u64();
556                        workspace.update(cx, |workspace, _cx| {
557                            workspace.set_database_id(workspace_id);
558                        });
559                        cx.background_spawn(async move {
560                            crate::persistence::DB
561                                .set_session_binding(workspace_id, session_id, Some(window_id))
562                                .await
563                                .log_err();
564                        })
565                        .detach();
566                    } else {
567                        cx.background_spawn(async move {
568                            crate::persistence::DB
569                                .delete_workspace_by_id(workspace_id)
570                                .await
571                                .log_err();
572                        })
573                        .detach();
574                    }
575                    this.serialize(cx);
576                }
577                Err(error) => {
578                    log::error!("Failed to create workspace: {error:#}");
579                    if let Some(index) = weak_workspace
580                        .upgrade()
581                        .and_then(|w| this.workspaces.iter().position(|ws| *ws == w))
582                    {
583                        this.remove_workspace(index, window, cx);
584                    }
585                    this.workspace().update(cx, |workspace, cx| {
586                        let id = NotificationId::unique::<MultiWorkspace>();
587                        workspace.show_toast(
588                            Toast::new(id, format!("Failed to create workspace: {error}")),
589                            cx,
590                        );
591                    });
592                }
593            })
594            .log_err();
595        }));
596    }
597
598    pub fn remove_workspace(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
599        if self.workspaces.len() <= 1 || index >= self.workspaces.len() {
600            return;
601        }
602
603        let removed_workspace = self.workspaces.remove(index);
604
605        if self.active_workspace_index >= self.workspaces.len() {
606            self.active_workspace_index = self.workspaces.len() - 1;
607        } else if self.active_workspace_index > index {
608            self.active_workspace_index -= 1;
609        }
610
611        if let Some(workspace_id) = removed_workspace.read(cx).database_id() {
612            self.pending_removal_tasks.retain(|task| !task.is_ready());
613            self.pending_removal_tasks
614                .push(cx.background_spawn(async move {
615                    crate::persistence::DB
616                        .delete_workspace_by_id(workspace_id)
617                        .await
618                        .log_err();
619                }));
620        }
621
622        self.serialize(cx);
623        self.focus_active_workspace(window, cx);
624        cx.notify();
625    }
626
627    pub fn open_project(
628        &mut self,
629        paths: Vec<PathBuf>,
630        window: &mut Window,
631        cx: &mut Context<Self>,
632    ) -> Task<Result<()>> {
633        let workspace = self.workspace().clone();
634
635        if self.multi_workspace_enabled(cx) {
636            workspace.update(cx, |workspace, cx| {
637                workspace.open_workspace_for_paths(true, paths, window, cx)
638            })
639        } else {
640            cx.spawn_in(window, async move |_this, cx| {
641                let should_continue = workspace
642                    .update_in(cx, |workspace, window, cx| {
643                        workspace.prepare_to_close(crate::CloseIntent::ReplaceWindow, window, cx)
644                    })?
645                    .await?;
646                if should_continue {
647                    workspace
648                        .update_in(cx, |workspace, window, cx| {
649                            workspace.open_workspace_for_paths(true, paths, window, cx)
650                        })?
651                        .await
652                } else {
653                    Ok(())
654                }
655            })
656        }
657    }
658}
659
660impl Render for MultiWorkspace {
661    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
662        let multi_workspace_enabled = self.multi_workspace_enabled(cx);
663        let is_zoomed = self.workspace().read(cx).zoomed_item().is_some();
664
665        let sidebar: Option<AnyElement> = if multi_workspace_enabled && self.sidebar_open {
666            self.sidebar.as_ref().map(|sidebar_handle| {
667                let weak = cx.weak_entity();
668
669                let sidebar_width = sidebar_handle.width(cx);
670                let resize_handle = deferred(
671                    div()
672                        .id("sidebar-resize-handle")
673                        .absolute()
674                        .right(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
675                        .top(px(0.))
676                        .h_full()
677                        .w(SIDEBAR_RESIZE_HANDLE_SIZE)
678                        .cursor_col_resize()
679                        .on_drag(DraggedSidebar, |dragged, _, _, cx| {
680                            cx.stop_propagation();
681                            cx.new(|_| dragged.clone())
682                        })
683                        .on_mouse_down(MouseButton::Left, |_, _, cx| {
684                            cx.stop_propagation();
685                        })
686                        .on_mouse_up(MouseButton::Left, move |event, _, cx| {
687                            if event.click_count == 2 {
688                                weak.update(cx, |this, cx| {
689                                    if let Some(sidebar) = this.sidebar.as_mut() {
690                                        sidebar.set_width(None, cx);
691                                    }
692                                })
693                                .ok();
694                                cx.stop_propagation();
695                            }
696                        })
697                        .occlude(),
698                );
699
700                div()
701                    .id("sidebar-container")
702                    .relative()
703                    .h_full()
704                    .w(sidebar_width)
705                    .flex_shrink_0()
706                    .child(sidebar_handle.to_any())
707                    .child(resize_handle)
708                    .into_any_element()
709            })
710        } else {
711            None
712        };
713
714        client_side_decorations(
715            h_flex()
716                .key_context("Workspace")
717                .relative()
718                .size_full()
719                .on_action(cx.listener(Self::close_window))
720                .on_action(
721                    cx.listener(|this: &mut Self, _: &NewWorkspaceInWindow, window, cx| {
722                        this.create_workspace(window, cx);
723                    }),
724                )
725                .on_action(
726                    cx.listener(|this: &mut Self, _: &NextWorkspaceInWindow, window, cx| {
727                        this.activate_next_workspace(window, cx);
728                    }),
729                )
730                .on_action(cx.listener(
731                    |this: &mut Self, _: &PreviousWorkspaceInWindow, window, cx| {
732                        this.activate_previous_workspace(window, cx);
733                    },
734                ))
735                .on_action(cx.listener(
736                    |this: &mut Self, _: &ToggleWorkspaceSidebar, window, cx| {
737                        this.toggle_sidebar(window, cx);
738                    },
739                ))
740                .on_action(
741                    cx.listener(|this: &mut Self, _: &FocusWorkspaceSidebar, window, cx| {
742                        this.focus_sidebar(window, cx);
743                    }),
744                )
745                .when(
746                    self.sidebar_open() && self.multi_workspace_enabled(cx),
747                    |this| {
748                        this.on_drag_move(cx.listener(
749                            |this: &mut Self, e: &DragMoveEvent<DraggedSidebar>, _window, cx| {
750                                if let Some(sidebar) = &this.sidebar {
751                                    let new_width = e.event.position.x;
752                                    sidebar.set_width(Some(new_width), cx);
753                                }
754                            },
755                        ))
756                        .children(sidebar)
757                    },
758                )
759                .child(
760                    div()
761                        .flex()
762                        .flex_1()
763                        .size_full()
764                        .overflow_hidden()
765                        .when(is_zoomed, |this| this.absolute().inset_0())
766                        .child(self.workspace().clone()),
767                ),
768            window,
769            cx,
770            Tiling {
771                left: multi_workspace_enabled && self.sidebar_open && !is_zoomed,
772                ..Tiling::default()
773            },
774        )
775    }
776}