multi_workspace.rs

   1use anyhow::Result;
   2use feature_flags::{AgentV2FeatureFlag, FeatureFlagAppExt};
   3use gpui::PathPromptOptions;
   4use gpui::{
   5    AnyView, App, Context, DragMoveEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
   6    ManagedView, MouseButton, Pixels, Render, Subscription, Task, Tiling, Window, WindowId,
   7    actions, deferred, px,
   8};
   9use project::{DirectoryLister, DisableAiSettings, Project, ProjectGroupKey};
  10use settings::Settings;
  11pub use settings::SidebarSide;
  12use std::future::Future;
  13use std::path::Path;
  14use std::path::PathBuf;
  15use std::sync::Arc;
  16use ui::prelude::*;
  17use util::ResultExt;
  18use util::path_list::PathList;
  19use zed_actions::agents_sidebar::{MoveWorkspaceToNewWindow, ToggleThreadSwitcher};
  20
  21use agent_settings::AgentSettings;
  22use settings::SidebarDockPosition;
  23use ui::{ContextMenu, right_click_menu};
  24
  25const SIDEBAR_RESIZE_HANDLE_SIZE: Pixels = px(6.0);
  26
  27use crate::AppState;
  28use crate::{
  29    CloseIntent, CloseWindow, DockPosition, Event as WorkspaceEvent, Item, ModalView, OpenMode,
  30    Panel, Workspace, WorkspaceId, client_side_decorations,
  31    persistence::model::MultiWorkspaceState,
  32};
  33
  34actions!(
  35    multi_workspace,
  36    [
  37        /// Toggles the workspace switcher sidebar.
  38        ToggleWorkspaceSidebar,
  39        /// Closes the workspace sidebar.
  40        CloseWorkspaceSidebar,
  41        /// Moves focus to or from the workspace sidebar without closing it.
  42        FocusWorkspaceSidebar,
  43        //TODO: Restore next/previous workspace
  44    ]
  45);
  46
  47#[derive(Default)]
  48pub struct SidebarRenderState {
  49    pub open: bool,
  50    pub side: SidebarSide,
  51}
  52
  53pub fn sidebar_side_context_menu(
  54    id: impl Into<ElementId>,
  55    cx: &App,
  56) -> ui::RightClickMenu<ContextMenu> {
  57    let current_position = AgentSettings::get_global(cx).sidebar_side;
  58    right_click_menu(id).menu(move |window, cx| {
  59        let fs = <dyn fs::Fs>::global(cx);
  60        ContextMenu::build(window, cx, move |mut menu, _, _cx| {
  61            let positions: [(SidebarDockPosition, &str); 2] = [
  62                (SidebarDockPosition::Left, "Left"),
  63                (SidebarDockPosition::Right, "Right"),
  64            ];
  65            for (position, label) in positions {
  66                let fs = fs.clone();
  67                menu = menu.toggleable_entry(
  68                    label,
  69                    position == current_position,
  70                    IconPosition::Start,
  71                    None,
  72                    move |_window, cx| {
  73                        settings::update_settings_file(fs.clone(), cx, move |settings, _cx| {
  74                            settings
  75                                .agent
  76                                .get_or_insert_default()
  77                                .set_sidebar_side(position);
  78                        });
  79                    },
  80                );
  81            }
  82            menu
  83        })
  84    })
  85}
  86
  87pub enum MultiWorkspaceEvent {
  88    ActiveWorkspaceChanged,
  89    WorkspaceAdded(Entity<Workspace>),
  90    WorkspaceRemoved(EntityId),
  91}
  92
  93pub enum SidebarEvent {
  94    SerializeNeeded,
  95}
  96
  97pub trait Sidebar: Focusable + Render + EventEmitter<SidebarEvent> + Sized {
  98    fn width(&self, cx: &App) -> Pixels;
  99    fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
 100    fn has_notifications(&self, cx: &App) -> bool;
 101    fn side(&self, _cx: &App) -> SidebarSide;
 102
 103    fn is_threads_list_view_active(&self) -> bool {
 104        true
 105    }
 106    /// Makes focus reset back to the search editor upon toggling the sidebar from outside
 107    fn prepare_for_focus(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
 108    /// Opens or cycles the thread switcher popup.
 109    fn toggle_thread_switcher(
 110        &mut self,
 111        _select_last: bool,
 112        _window: &mut Window,
 113        _cx: &mut Context<Self>,
 114    ) {
 115    }
 116
 117    /// Return an opaque JSON blob of sidebar-specific state to persist.
 118    fn serialized_state(&self, _cx: &App) -> Option<String> {
 119        None
 120    }
 121
 122    /// Restore sidebar state from a previously-serialized blob.
 123    fn restore_serialized_state(
 124        &mut self,
 125        _state: &str,
 126        _window: &mut Window,
 127        _cx: &mut Context<Self>,
 128    ) {
 129    }
 130}
 131
 132pub trait SidebarHandle: 'static + Send + Sync {
 133    fn width(&self, cx: &App) -> Pixels;
 134    fn set_width(&self, width: Option<Pixels>, cx: &mut App);
 135    fn focus_handle(&self, cx: &App) -> FocusHandle;
 136    fn focus(&self, window: &mut Window, cx: &mut App);
 137    fn prepare_for_focus(&self, window: &mut Window, cx: &mut App);
 138    fn has_notifications(&self, cx: &App) -> bool;
 139    fn to_any(&self) -> AnyView;
 140    fn entity_id(&self) -> EntityId;
 141    fn toggle_thread_switcher(&self, select_last: bool, window: &mut Window, cx: &mut App);
 142
 143    fn is_threads_list_view_active(&self, cx: &App) -> bool;
 144
 145    fn side(&self, cx: &App) -> SidebarSide;
 146    fn serialized_state(&self, cx: &App) -> Option<String>;
 147    fn restore_serialized_state(&self, state: &str, window: &mut Window, cx: &mut App);
 148}
 149
 150#[derive(Clone)]
 151pub struct DraggedSidebar;
 152
 153impl Render for DraggedSidebar {
 154    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
 155        gpui::Empty
 156    }
 157}
 158
 159impl<T: Sidebar> SidebarHandle for Entity<T> {
 160    fn width(&self, cx: &App) -> Pixels {
 161        self.read(cx).width(cx)
 162    }
 163
 164    fn set_width(&self, width: Option<Pixels>, cx: &mut App) {
 165        self.update(cx, |this, cx| this.set_width(width, cx))
 166    }
 167
 168    fn focus_handle(&self, cx: &App) -> FocusHandle {
 169        self.read(cx).focus_handle(cx)
 170    }
 171
 172    fn focus(&self, window: &mut Window, cx: &mut App) {
 173        let handle = self.read(cx).focus_handle(cx);
 174        window.focus(&handle, cx);
 175    }
 176
 177    fn prepare_for_focus(&self, window: &mut Window, cx: &mut App) {
 178        self.update(cx, |this, cx| this.prepare_for_focus(window, cx));
 179    }
 180
 181    fn has_notifications(&self, cx: &App) -> bool {
 182        self.read(cx).has_notifications(cx)
 183    }
 184
 185    fn to_any(&self) -> AnyView {
 186        self.clone().into()
 187    }
 188
 189    fn entity_id(&self) -> EntityId {
 190        Entity::entity_id(self)
 191    }
 192
 193    fn toggle_thread_switcher(&self, select_last: bool, window: &mut Window, cx: &mut App) {
 194        let entity = self.clone();
 195        window.defer(cx, move |window, cx| {
 196            entity.update(cx, |this, cx| {
 197                this.toggle_thread_switcher(select_last, window, cx);
 198            });
 199        });
 200    }
 201
 202    fn is_threads_list_view_active(&self, cx: &App) -> bool {
 203        self.read(cx).is_threads_list_view_active()
 204    }
 205
 206    fn side(&self, cx: &App) -> SidebarSide {
 207        self.read(cx).side(cx)
 208    }
 209
 210    fn serialized_state(&self, cx: &App) -> Option<String> {
 211        self.read(cx).serialized_state(cx)
 212    }
 213
 214    fn restore_serialized_state(&self, state: &str, window: &mut Window, cx: &mut App) {
 215        self.update(cx, |this, cx| {
 216            this.restore_serialized_state(state, window, cx)
 217        })
 218    }
 219}
 220
 221/// Tracks which workspace the user is currently looking at.
 222///
 223/// `Persistent` workspaces live in the `workspaces` vec and are shown in the
 224/// sidebar. `Transient` workspaces exist outside the vec and are discarded
 225/// when the user switches away.
 226enum ActiveWorkspace {
 227    /// A persistent workspace, identified by index into the `workspaces` vec.
 228    Persistent(usize),
 229    /// A workspace not in the `workspaces` vec that will be discarded on
 230    /// switch or promoted to persistent when the sidebar is opened.
 231    Transient(Entity<Workspace>),
 232}
 233
 234impl ActiveWorkspace {
 235    fn persistent_index(&self) -> Option<usize> {
 236        match self {
 237            Self::Persistent(index) => Some(*index),
 238            Self::Transient(_) => None,
 239        }
 240    }
 241
 242    fn transient_workspace(&self) -> Option<&Entity<Workspace>> {
 243        match self {
 244            Self::Transient(workspace) => Some(workspace),
 245            Self::Persistent(_) => None,
 246        }
 247    }
 248
 249    /// Sets the active workspace to transient, returning the previous
 250    /// transient workspace (if any).
 251    fn set_transient(&mut self, workspace: Entity<Workspace>) -> Option<Entity<Workspace>> {
 252        match std::mem::replace(self, Self::Transient(workspace)) {
 253            Self::Transient(old) => Some(old),
 254            Self::Persistent(_) => None,
 255        }
 256    }
 257
 258    /// Sets the active workspace to persistent at the given index,
 259    /// returning the previous transient workspace (if any).
 260    fn set_persistent(&mut self, index: usize) -> Option<Entity<Workspace>> {
 261        match std::mem::replace(self, Self::Persistent(index)) {
 262            Self::Transient(workspace) => Some(workspace),
 263            Self::Persistent(_) => None,
 264        }
 265    }
 266}
 267
 268pub struct MultiWorkspace {
 269    window_id: WindowId,
 270    workspaces: Vec<Entity<Workspace>>,
 271    active_workspace: ActiveWorkspace,
 272    project_group_keys: Vec<ProjectGroupKey>,
 273    sidebar: Option<Box<dyn SidebarHandle>>,
 274    sidebar_open: bool,
 275    sidebar_overlay: Option<AnyView>,
 276    pending_removal_tasks: Vec<Task<()>>,
 277    _serialize_task: Option<Task<()>>,
 278    _subscriptions: Vec<Subscription>,
 279}
 280
 281impl EventEmitter<MultiWorkspaceEvent> for MultiWorkspace {}
 282
 283impl MultiWorkspace {
 284    pub fn sidebar_side(&self, cx: &App) -> SidebarSide {
 285        self.sidebar
 286            .as_ref()
 287            .map_or(SidebarSide::Left, |s| s.side(cx))
 288    }
 289
 290    pub fn sidebar_render_state(&self, cx: &App) -> SidebarRenderState {
 291        SidebarRenderState {
 292            open: self.sidebar_open() && self.multi_workspace_enabled(cx),
 293            side: self.sidebar_side(cx),
 294        }
 295    }
 296
 297    pub fn new(workspace: Entity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
 298        let release_subscription = cx.on_release(|this: &mut MultiWorkspace, _cx| {
 299            if let Some(task) = this._serialize_task.take() {
 300                task.detach();
 301            }
 302            for task in std::mem::take(&mut this.pending_removal_tasks) {
 303                task.detach();
 304            }
 305        });
 306        let quit_subscription = cx.on_app_quit(Self::app_will_quit);
 307        let settings_subscription = cx.observe_global_in::<settings::SettingsStore>(window, {
 308            let mut previous_disable_ai = DisableAiSettings::get_global(cx).disable_ai;
 309            move |this, window, cx| {
 310                if DisableAiSettings::get_global(cx).disable_ai != previous_disable_ai {
 311                    this.collapse_to_single_workspace(window, cx);
 312                    previous_disable_ai = DisableAiSettings::get_global(cx).disable_ai;
 313                }
 314            }
 315        });
 316        Self::subscribe_to_workspace(&workspace, window, cx);
 317        let weak_self = cx.weak_entity();
 318        workspace.update(cx, |workspace, cx| {
 319            workspace.set_multi_workspace(weak_self, cx);
 320        });
 321        Self {
 322            window_id: window.window_handle().window_id(),
 323            project_group_keys: Vec::new(),
 324            workspaces: Vec::new(),
 325            active_workspace: ActiveWorkspace::Transient(workspace),
 326            sidebar: None,
 327            sidebar_open: false,
 328            sidebar_overlay: None,
 329            pending_removal_tasks: Vec::new(),
 330            _serialize_task: None,
 331            _subscriptions: vec![
 332                release_subscription,
 333                quit_subscription,
 334                settings_subscription,
 335            ],
 336        }
 337    }
 338
 339    pub fn register_sidebar<T: Sidebar>(&mut self, sidebar: Entity<T>, cx: &mut Context<Self>) {
 340        self._subscriptions
 341            .push(cx.observe(&sidebar, |_this, _, cx| {
 342                cx.notify();
 343            }));
 344        self._subscriptions
 345            .push(cx.subscribe(&sidebar, |this, _, event, cx| match event {
 346                SidebarEvent::SerializeNeeded => {
 347                    this.serialize(cx);
 348                }
 349            }));
 350        self.sidebar = Some(Box::new(sidebar));
 351    }
 352
 353    pub fn sidebar(&self) -> Option<&dyn SidebarHandle> {
 354        self.sidebar.as_deref()
 355    }
 356
 357    pub fn set_sidebar_overlay(&mut self, overlay: Option<AnyView>, cx: &mut Context<Self>) {
 358        self.sidebar_overlay = overlay;
 359        cx.notify();
 360    }
 361
 362    pub fn sidebar_open(&self) -> bool {
 363        self.sidebar_open
 364    }
 365
 366    pub fn sidebar_has_notifications(&self, cx: &App) -> bool {
 367        self.sidebar
 368            .as_ref()
 369            .map_or(false, |s| s.has_notifications(cx))
 370    }
 371
 372    pub fn is_threads_list_view_active(&self, cx: &App) -> bool {
 373        self.sidebar
 374            .as_ref()
 375            .map_or(false, |s| s.is_threads_list_view_active(cx))
 376    }
 377
 378    pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
 379        cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
 380    }
 381
 382    pub fn toggle_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 383        if !self.multi_workspace_enabled(cx) {
 384            return;
 385        }
 386
 387        if self.sidebar_open() {
 388            self.close_sidebar(window, cx);
 389        } else {
 390            self.open_sidebar(cx);
 391            if let Some(sidebar) = &self.sidebar {
 392                sidebar.prepare_for_focus(window, cx);
 393                sidebar.focus(window, cx);
 394            }
 395        }
 396    }
 397
 398    pub fn close_sidebar_action(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 399        if !self.multi_workspace_enabled(cx) {
 400            return;
 401        }
 402
 403        if self.sidebar_open() {
 404            self.close_sidebar(window, cx);
 405        }
 406    }
 407
 408    pub fn focus_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 409        if !self.multi_workspace_enabled(cx) {
 410            return;
 411        }
 412
 413        if self.sidebar_open() {
 414            let sidebar_is_focused = self
 415                .sidebar
 416                .as_ref()
 417                .is_some_and(|s| s.focus_handle(cx).contains_focused(window, cx));
 418
 419            if sidebar_is_focused {
 420                let pane = self.workspace().read(cx).active_pane().clone();
 421                let pane_focus = pane.read(cx).focus_handle(cx);
 422                window.focus(&pane_focus, cx);
 423            } else if let Some(sidebar) = &self.sidebar {
 424                sidebar.prepare_for_focus(window, cx);
 425                sidebar.focus(window, cx);
 426            }
 427        } else {
 428            self.open_sidebar(cx);
 429            if let Some(sidebar) = &self.sidebar {
 430                sidebar.prepare_for_focus(window, cx);
 431                sidebar.focus(window, cx);
 432            }
 433        }
 434    }
 435
 436    pub fn open_sidebar(&mut self, cx: &mut Context<Self>) {
 437        self.sidebar_open = true;
 438        if let ActiveWorkspace::Transient(workspace) = &self.active_workspace {
 439            let workspace = workspace.clone();
 440            let index = self.promote_transient(workspace, cx);
 441            self.active_workspace = ActiveWorkspace::Persistent(index);
 442        }
 443        let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
 444        for workspace in self.workspaces.iter() {
 445            workspace.update(cx, |workspace, _cx| {
 446                workspace.set_sidebar_focus_handle(sidebar_focus_handle.clone());
 447            });
 448        }
 449        self.serialize(cx);
 450        cx.notify();
 451    }
 452
 453    pub fn close_sidebar(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 454        self.sidebar_open = false;
 455        for workspace in self.workspaces.iter() {
 456            workspace.update(cx, |workspace, _cx| {
 457                workspace.set_sidebar_focus_handle(None);
 458            });
 459        }
 460        let pane = self.workspace().read(cx).active_pane().clone();
 461        let pane_focus = pane.read(cx).focus_handle(cx);
 462        window.focus(&pane_focus, cx);
 463        self.serialize(cx);
 464        cx.notify();
 465    }
 466
 467    pub fn close_window(&mut self, _: &CloseWindow, window: &mut Window, cx: &mut Context<Self>) {
 468        cx.spawn_in(window, async move |this, cx| {
 469            let workspaces = this.update(cx, |multi_workspace, _cx| {
 470                multi_workspace.workspaces().cloned().collect::<Vec<_>>()
 471            })?;
 472
 473            for workspace in workspaces {
 474                let should_continue = workspace
 475                    .update_in(cx, |workspace, window, cx| {
 476                        workspace.prepare_to_close(CloseIntent::CloseWindow, window, cx)
 477                    })?
 478                    .await?;
 479                if !should_continue {
 480                    return anyhow::Ok(());
 481                }
 482            }
 483
 484            cx.update(|window, _cx| {
 485                window.remove_window();
 486            })?;
 487
 488            anyhow::Ok(())
 489        })
 490        .detach_and_log_err(cx);
 491    }
 492
 493    fn subscribe_to_workspace(
 494        workspace: &Entity<Workspace>,
 495        window: &Window,
 496        cx: &mut Context<Self>,
 497    ) {
 498        let project = workspace.read(cx).project().clone();
 499        cx.subscribe_in(&project, window, {
 500            let workspace = workspace.downgrade();
 501            move |this, _project, event, _window, cx| match event {
 502                project::Event::WorktreeAdded(_) | project::Event::WorktreeRemoved(_) => {
 503                    if let Some(workspace) = workspace.upgrade() {
 504                        this.add_project_group_key(workspace.read(cx).project_group_key(cx));
 505                    }
 506                }
 507                _ => {}
 508            }
 509        })
 510        .detach();
 511
 512        cx.subscribe_in(workspace, window, |this, workspace, event, window, cx| {
 513            if let WorkspaceEvent::Activate = event {
 514                this.activate(workspace.clone(), window, cx);
 515            }
 516        })
 517        .detach();
 518    }
 519
 520    pub fn add_project_group_key(&mut self, project_group_key: ProjectGroupKey) {
 521        if project_group_key.path_list().paths().is_empty() {
 522            return;
 523        }
 524        if self.project_group_keys.contains(&project_group_key) {
 525            return;
 526        }
 527        self.project_group_keys.push(project_group_key);
 528    }
 529
 530    pub fn restore_project_group_keys(&mut self, keys: Vec<ProjectGroupKey>) {
 531        let mut restored = keys;
 532        for existing_key in &self.project_group_keys {
 533            if !restored.contains(existing_key) {
 534                restored.push(existing_key.clone());
 535            }
 536        }
 537        self.project_group_keys = restored;
 538    }
 539
 540    pub fn project_group_keys(&self) -> impl Iterator<Item = &ProjectGroupKey> {
 541        self.project_group_keys.iter()
 542    }
 543
 544    /// Returns the project groups, ordered by most recently added.
 545    pub fn project_groups(
 546        &self,
 547        cx: &App,
 548    ) -> impl Iterator<Item = (ProjectGroupKey, Vec<Entity<Workspace>>)> {
 549        let mut groups = self
 550            .project_group_keys
 551            .iter()
 552            .rev()
 553            .map(|key| (key.clone(), Vec::new()))
 554            .collect::<Vec<_>>();
 555        for workspace in &self.workspaces {
 556            let key = workspace.read(cx).project_group_key(cx);
 557            if let Some((_, workspaces)) = groups.iter_mut().find(|(k, _)| k == &key) {
 558                workspaces.push(workspace.clone());
 559            }
 560        }
 561        groups.into_iter()
 562    }
 563
 564    pub fn workspaces_for_project_group(
 565        &self,
 566        project_group_key: &ProjectGroupKey,
 567        cx: &App,
 568    ) -> impl Iterator<Item = &Entity<Workspace>> {
 569        self.workspaces
 570            .iter()
 571            .filter(move |ws| ws.read(cx).project_group_key(cx) == *project_group_key)
 572    }
 573
 574    pub fn remove_folder_from_project_group(
 575        &mut self,
 576        project_group_key: &ProjectGroupKey,
 577        path: &Path,
 578        cx: &mut Context<Self>,
 579    ) {
 580        let new_path_list = project_group_key.path_list().without_path(path);
 581        if new_path_list.is_empty() {
 582            return;
 583        }
 584
 585        let new_key = ProjectGroupKey::new(project_group_key.host(), new_path_list);
 586
 587        let workspaces: Vec<_> = self
 588            .workspaces_for_project_group(project_group_key, cx)
 589            .cloned()
 590            .collect();
 591
 592        self.add_project_group_key(new_key);
 593
 594        for workspace in workspaces {
 595            let project = workspace.read(cx).project().clone();
 596            project.update(cx, |project, cx| {
 597                project.remove_worktree_for_main_worktree_path(path, cx);
 598            });
 599        }
 600
 601        self.serialize(cx);
 602        cx.notify();
 603    }
 604
 605    pub fn prompt_to_add_folders_to_project_group(
 606        &mut self,
 607        key: &ProjectGroupKey,
 608        window: &mut Window,
 609        cx: &mut Context<Self>,
 610    ) {
 611        let paths = self.workspace().update(cx, |workspace, cx| {
 612            workspace.prompt_for_open_path(
 613                PathPromptOptions {
 614                    files: false,
 615                    directories: true,
 616                    multiple: true,
 617                    prompt: None,
 618                },
 619                DirectoryLister::Project(workspace.project().clone()),
 620                window,
 621                cx,
 622            )
 623        });
 624
 625        let key = key.clone();
 626        cx.spawn_in(window, async move |this, cx| {
 627            if let Some(new_paths) = paths.await.ok().flatten() {
 628                if !new_paths.is_empty() {
 629                    this.update(cx, |multi_workspace, cx| {
 630                        multi_workspace.add_folders_to_project_group(&key, new_paths, cx);
 631                    })?;
 632                }
 633            }
 634            anyhow::Ok(())
 635        })
 636        .detach_and_log_err(cx);
 637    }
 638
 639    pub fn add_folders_to_project_group(
 640        &mut self,
 641        project_group_key: &ProjectGroupKey,
 642        new_paths: Vec<PathBuf>,
 643        cx: &mut Context<Self>,
 644    ) {
 645        let mut all_paths: Vec<PathBuf> = project_group_key.path_list().paths().to_vec();
 646        all_paths.extend(new_paths.iter().cloned());
 647        let new_path_list = PathList::new(&all_paths);
 648        let new_key = ProjectGroupKey::new(project_group_key.host(), new_path_list);
 649
 650        let workspaces: Vec<_> = self
 651            .workspaces_for_project_group(project_group_key, cx)
 652            .cloned()
 653            .collect();
 654
 655        self.add_project_group_key(new_key);
 656
 657        for workspace in workspaces {
 658            let project = workspace.read(cx).project().clone();
 659            for path in &new_paths {
 660                project
 661                    .update(cx, |project, cx| {
 662                        project.find_or_create_worktree(path, true, cx)
 663                    })
 664                    .detach_and_log_err(cx);
 665            }
 666        }
 667
 668        self.serialize(cx);
 669        cx.notify();
 670    }
 671
 672    pub fn remove_project_group(
 673        &mut self,
 674        key: &ProjectGroupKey,
 675        window: &mut Window,
 676        cx: &mut Context<Self>,
 677    ) {
 678        self.project_group_keys.retain(|k| k != key);
 679
 680        let workspaces: Vec<_> = self
 681            .workspaces_for_project_group(key, cx)
 682            .cloned()
 683            .collect();
 684        for workspace in workspaces {
 685            self.remove(&workspace, window, cx);
 686        }
 687
 688        self.serialize(cx);
 689        cx.notify();
 690    }
 691
 692    /// Finds an existing workspace in this multi-workspace whose paths match,
 693    /// or creates a new one (deserializing its saved state from the database).
 694    /// Never searches other windows or matches workspaces with a superset of
 695    /// the requested paths.
 696    pub fn find_or_create_local_workspace(
 697        &mut self,
 698        path_list: PathList,
 699        window: &mut Window,
 700        cx: &mut Context<Self>,
 701    ) -> Task<Result<Entity<Workspace>>> {
 702        if let Some(workspace) = self
 703            .workspaces
 704            .iter()
 705            .find(|ws| PathList::new(&ws.read(cx).root_paths(cx)) == path_list)
 706            .cloned()
 707        {
 708            self.activate(workspace.clone(), window, cx);
 709            return Task::ready(Ok(workspace));
 710        }
 711
 712        if let Some(transient) = self.active_workspace.transient_workspace() {
 713            if transient.read(cx).project_group_key(cx).path_list() == &path_list {
 714                return Task::ready(Ok(transient.clone()));
 715            }
 716        }
 717
 718        let paths = path_list.paths().to_vec();
 719        let app_state = self.workspace().read(cx).app_state().clone();
 720        let requesting_window = window.window_handle().downcast::<MultiWorkspace>();
 721
 722        cx.spawn(async move |_this, cx| {
 723            let result = cx
 724                .update(|cx| {
 725                    Workspace::new_local(
 726                        paths,
 727                        app_state,
 728                        requesting_window,
 729                        None,
 730                        None,
 731                        OpenMode::Activate,
 732                        cx,
 733                    )
 734                })
 735                .await?;
 736            Ok(result.workspace)
 737        })
 738    }
 739
 740    pub fn workspace(&self) -> &Entity<Workspace> {
 741        match &self.active_workspace {
 742            ActiveWorkspace::Persistent(index) => &self.workspaces[*index],
 743            ActiveWorkspace::Transient(workspace) => workspace,
 744        }
 745    }
 746
 747    pub fn workspaces(&self) -> impl Iterator<Item = &Entity<Workspace>> {
 748        self.workspaces
 749            .iter()
 750            .chain(self.active_workspace.transient_workspace())
 751    }
 752
 753    /// Adds a workspace to this window as persistent without changing which
 754    /// workspace is active. Unlike `activate()`, this always inserts into the
 755    /// persistent list regardless of sidebar state — it's used for system-
 756    /// initiated additions like deserialization and worktree discovery.
 757    pub fn add(&mut self, workspace: Entity<Workspace>, window: &Window, cx: &mut Context<Self>) {
 758        self.insert_workspace(workspace, window, cx);
 759    }
 760
 761    /// Ensures the workspace is in the multiworkspace and makes it the active one.
 762    pub fn activate(
 763        &mut self,
 764        workspace: Entity<Workspace>,
 765        window: &mut Window,
 766        cx: &mut Context<Self>,
 767    ) {
 768        // Re-activating the current workspace is a no-op.
 769        if self.workspace() == &workspace {
 770            self.focus_active_workspace(window, cx);
 771            return;
 772        }
 773
 774        // Resolve where we're going.
 775        let new_index = if let Some(index) = self.workspaces.iter().position(|w| *w == workspace) {
 776            Some(index)
 777        } else if self.sidebar_open {
 778            Some(self.insert_workspace(workspace.clone(), &*window, cx))
 779        } else {
 780            None
 781        };
 782
 783        // Transition the active workspace.
 784        if let Some(index) = new_index {
 785            if let Some(old) = self.active_workspace.set_persistent(index) {
 786                if self.sidebar_open {
 787                    self.promote_transient(old, cx);
 788                } else {
 789                    self.detach_workspace(&old, cx);
 790                    cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(old.entity_id()));
 791                }
 792            }
 793        } else {
 794            Self::subscribe_to_workspace(&workspace, window, cx);
 795            let weak_self = cx.weak_entity();
 796            workspace.update(cx, |workspace, cx| {
 797                workspace.set_multi_workspace(weak_self, cx);
 798            });
 799            if let Some(old) = self.active_workspace.set_transient(workspace) {
 800                self.detach_workspace(&old, cx);
 801                cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(old.entity_id()));
 802            }
 803        }
 804
 805        cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
 806        self.serialize(cx);
 807        self.focus_active_workspace(window, cx);
 808        cx.notify();
 809    }
 810
 811    /// Promotes a former transient workspace into the persistent list.
 812    /// Returns the index of the newly inserted workspace.
 813    fn promote_transient(&mut self, workspace: Entity<Workspace>, cx: &mut Context<Self>) -> usize {
 814        let project_group_key = workspace.read(cx).project().read(cx).project_group_key(cx);
 815        self.add_project_group_key(project_group_key);
 816        self.workspaces.push(workspace.clone());
 817        cx.emit(MultiWorkspaceEvent::WorkspaceAdded(workspace));
 818        self.workspaces.len() - 1
 819    }
 820
 821    /// Collapses to a single transient workspace, discarding all persistent
 822    /// workspaces. Used when multi-workspace is disabled (e.g. disable_ai).
 823    fn collapse_to_single_workspace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 824        if self.sidebar_open {
 825            self.close_sidebar(window, cx);
 826        }
 827        let active = self.workspace().clone();
 828        for workspace in std::mem::take(&mut self.workspaces) {
 829            if workspace != active {
 830                self.detach_workspace(&workspace, cx);
 831                cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(workspace.entity_id()));
 832            }
 833        }
 834        self.project_group_keys.clear();
 835        self.active_workspace = ActiveWorkspace::Transient(active);
 836        cx.notify();
 837    }
 838
 839    /// Inserts a workspace into the list if not already present. Returns the
 840    /// index of the workspace (existing or newly inserted). Does not change
 841    /// the active workspace index.
 842    fn insert_workspace(
 843        &mut self,
 844        workspace: Entity<Workspace>,
 845        window: &Window,
 846        cx: &mut Context<Self>,
 847    ) -> usize {
 848        if let Some(index) = self.workspaces.iter().position(|w| *w == workspace) {
 849            index
 850        } else {
 851            let project_group_key = workspace.read(cx).project().read(cx).project_group_key(cx);
 852
 853            Self::subscribe_to_workspace(&workspace, window, cx);
 854            self.sync_sidebar_to_workspace(&workspace, cx);
 855            let weak_self = cx.weak_entity();
 856            workspace.update(cx, |workspace, cx| {
 857                workspace.set_multi_workspace(weak_self, cx);
 858            });
 859
 860            self.add_project_group_key(project_group_key);
 861            self.workspaces.push(workspace.clone());
 862            cx.emit(MultiWorkspaceEvent::WorkspaceAdded(workspace));
 863            cx.notify();
 864            self.workspaces.len() - 1
 865        }
 866    }
 867
 868    /// Clears session state and DB binding for a workspace that is being
 869    /// removed or replaced. The DB row is preserved so the workspace still
 870    /// appears in the recent-projects list.
 871    fn detach_workspace(&mut self, workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
 872        workspace.update(cx, |workspace, _cx| {
 873            workspace.session_id.take();
 874            workspace._schedule_serialize_workspace.take();
 875            workspace._serialize_workspace_task.take();
 876        });
 877
 878        if let Some(workspace_id) = workspace.read(cx).database_id() {
 879            let db = crate::persistence::WorkspaceDb::global(cx);
 880            self.pending_removal_tasks.retain(|task| !task.is_ready());
 881            self.pending_removal_tasks
 882                .push(cx.background_spawn(async move {
 883                    db.set_session_binding(workspace_id, None, None)
 884                        .await
 885                        .log_err();
 886                }));
 887        }
 888    }
 889
 890    fn sync_sidebar_to_workspace(&self, workspace: &Entity<Workspace>, cx: &mut Context<Self>) {
 891        if self.sidebar_open() {
 892            let sidebar_focus_handle = self.sidebar.as_ref().map(|s| s.focus_handle(cx));
 893            workspace.update(cx, |workspace, _| {
 894                workspace.set_sidebar_focus_handle(sidebar_focus_handle);
 895            });
 896        }
 897    }
 898
 899    pub(crate) fn serialize(&mut self, cx: &mut Context<Self>) {
 900        self._serialize_task = Some(cx.spawn(async move |this, cx| {
 901            let Some((window_id, state)) = this
 902                .read_with(cx, |this, cx| {
 903                    let state = MultiWorkspaceState {
 904                        active_workspace_id: this.workspace().read(cx).database_id(),
 905                        project_group_keys: this
 906                            .project_group_keys()
 907                            .cloned()
 908                            .map(Into::into)
 909                            .collect::<Vec<_>>(),
 910                        sidebar_open: this.sidebar_open,
 911                        sidebar_state: this.sidebar.as_ref().and_then(|s| s.serialized_state(cx)),
 912                    };
 913                    (this.window_id, state)
 914                })
 915                .ok()
 916            else {
 917                return;
 918            };
 919            let kvp = cx.update(|cx| db::kvp::KeyValueStore::global(cx));
 920            crate::persistence::write_multi_workspace_state(&kvp, window_id, state).await;
 921        }));
 922    }
 923
 924    /// Returns the in-flight serialization task (if any) so the caller can
 925    /// await it. Used by the quit handler to ensure pending DB writes
 926    /// complete before the process exits.
 927    pub fn flush_serialization(&mut self) -> Task<()> {
 928        self._serialize_task.take().unwrap_or(Task::ready(()))
 929    }
 930
 931    fn app_will_quit(&mut self, _cx: &mut Context<Self>) -> impl Future<Output = ()> + use<> {
 932        let mut tasks: Vec<Task<()>> = Vec::new();
 933        if let Some(task) = self._serialize_task.take() {
 934            tasks.push(task);
 935        }
 936        tasks.extend(std::mem::take(&mut self.pending_removal_tasks));
 937
 938        async move {
 939            futures::future::join_all(tasks).await;
 940        }
 941    }
 942
 943    pub fn focus_active_workspace(&self, window: &mut Window, cx: &mut App) {
 944        // If a dock panel is zoomed, focus it instead of the center pane.
 945        // Otherwise, focusing the center pane triggers dismiss_zoomed_items_to_reveal
 946        // which closes the zoomed dock.
 947        let focus_handle = {
 948            let workspace = self.workspace().read(cx);
 949            let mut target = None;
 950            for dock in workspace.all_docks() {
 951                let dock = dock.read(cx);
 952                if dock.is_open() {
 953                    if let Some(panel) = dock.active_panel() {
 954                        if panel.is_zoomed(window, cx) {
 955                            target = Some(panel.panel_focus_handle(cx));
 956                            break;
 957                        }
 958                    }
 959                }
 960            }
 961            target.unwrap_or_else(|| {
 962                let pane = workspace.active_pane().clone();
 963                pane.read(cx).focus_handle(cx)
 964            })
 965        };
 966        window.focus(&focus_handle, cx);
 967    }
 968
 969    pub fn panel<T: Panel>(&self, cx: &App) -> Option<Entity<T>> {
 970        self.workspace().read(cx).panel::<T>(cx)
 971    }
 972
 973    pub fn active_modal<V: ManagedView + 'static>(&self, cx: &App) -> Option<Entity<V>> {
 974        self.workspace().read(cx).active_modal::<V>(cx)
 975    }
 976
 977    pub fn add_panel<T: Panel>(
 978        &mut self,
 979        panel: Entity<T>,
 980        window: &mut Window,
 981        cx: &mut Context<Self>,
 982    ) {
 983        self.workspace().update(cx, |workspace, cx| {
 984            workspace.add_panel(panel, window, cx);
 985        });
 986    }
 987
 988    pub fn focus_panel<T: Panel>(
 989        &mut self,
 990        window: &mut Window,
 991        cx: &mut Context<Self>,
 992    ) -> Option<Entity<T>> {
 993        self.workspace()
 994            .update(cx, |workspace, cx| workspace.focus_panel::<T>(window, cx))
 995    }
 996
 997    // used in a test
 998    pub fn toggle_modal<V: ModalView, B>(
 999        &mut self,
1000        window: &mut Window,
1001        cx: &mut Context<Self>,
1002        build: B,
1003    ) where
1004        B: FnOnce(&mut Window, &mut gpui::Context<V>) -> V,
1005    {
1006        self.workspace().update(cx, |workspace, cx| {
1007            workspace.toggle_modal(window, cx, build);
1008        });
1009    }
1010
1011    pub fn toggle_dock(
1012        &mut self,
1013        dock_side: DockPosition,
1014        window: &mut Window,
1015        cx: &mut Context<Self>,
1016    ) {
1017        self.workspace().update(cx, |workspace, cx| {
1018            workspace.toggle_dock(dock_side, window, cx);
1019        });
1020    }
1021
1022    pub fn active_item_as<I: 'static>(&self, cx: &App) -> Option<Entity<I>> {
1023        self.workspace().read(cx).active_item_as::<I>(cx)
1024    }
1025
1026    pub fn items_of_type<'a, T: Item>(
1027        &'a self,
1028        cx: &'a App,
1029    ) -> impl 'a + Iterator<Item = Entity<T>> {
1030        self.workspace().read(cx).items_of_type::<T>(cx)
1031    }
1032
1033    pub fn database_id(&self, cx: &App) -> Option<WorkspaceId> {
1034        self.workspace().read(cx).database_id()
1035    }
1036
1037    pub fn take_pending_removal_tasks(&mut self) -> Vec<Task<()>> {
1038        let tasks: Vec<Task<()>> = std::mem::take(&mut self.pending_removal_tasks)
1039            .into_iter()
1040            .filter(|task| !task.is_ready())
1041            .collect();
1042        tasks
1043    }
1044
1045    #[cfg(any(test, feature = "test-support"))]
1046    pub fn set_random_database_id(&mut self, cx: &mut Context<Self>) {
1047        self.workspace().update(cx, |workspace, _cx| {
1048            workspace.set_random_database_id();
1049        });
1050    }
1051
1052    #[cfg(any(test, feature = "test-support"))]
1053    pub fn test_new(project: Entity<Project>, window: &mut Window, cx: &mut Context<Self>) -> Self {
1054        let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
1055        Self::new(workspace, window, cx)
1056    }
1057
1058    #[cfg(any(test, feature = "test-support"))]
1059    pub fn test_add_workspace(
1060        &mut self,
1061        project: Entity<Project>,
1062        window: &mut Window,
1063        cx: &mut Context<Self>,
1064    ) -> Entity<Workspace> {
1065        let workspace = cx.new(|cx| Workspace::test_new(project, window, cx));
1066        self.activate(workspace.clone(), window, cx);
1067        workspace
1068    }
1069
1070    #[cfg(any(test, feature = "test-support"))]
1071    pub fn create_test_workspace(
1072        &mut self,
1073        window: &mut Window,
1074        cx: &mut Context<Self>,
1075    ) -> Task<()> {
1076        let app_state = self.workspace().read(cx).app_state().clone();
1077        let project = Project::local(
1078            app_state.client.clone(),
1079            app_state.node_runtime.clone(),
1080            app_state.user_store.clone(),
1081            app_state.languages.clone(),
1082            app_state.fs.clone(),
1083            None,
1084            project::LocalProjectFlags::default(),
1085            cx,
1086        );
1087        let new_workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
1088        self.activate(new_workspace.clone(), window, cx);
1089
1090        let weak_workspace = new_workspace.downgrade();
1091        let db = crate::persistence::WorkspaceDb::global(cx);
1092        cx.spawn_in(window, async move |this, cx| {
1093            let workspace_id = db.next_id().await.unwrap();
1094            let workspace = weak_workspace.upgrade().unwrap();
1095            let task: Task<()> = this
1096                .update_in(cx, |this, window, cx| {
1097                    let session_id = workspace.read(cx).session_id();
1098                    let window_id = window.window_handle().window_id().as_u64();
1099                    workspace.update(cx, |workspace, _cx| {
1100                        workspace.set_database_id(workspace_id);
1101                    });
1102                    this.serialize(cx);
1103                    let db = db.clone();
1104                    cx.background_spawn(async move {
1105                        db.set_session_binding(workspace_id, session_id, Some(window_id))
1106                            .await
1107                            .log_err();
1108                    })
1109                })
1110                .unwrap();
1111            task.await
1112        })
1113    }
1114
1115    pub fn remove(
1116        &mut self,
1117        workspace: &Entity<Workspace>,
1118        window: &mut Window,
1119        cx: &mut Context<Self>,
1120    ) -> bool {
1121        let Some(index) = self.workspaces.iter().position(|w| w == workspace) else {
1122            return false;
1123        };
1124
1125        let old_key = workspace.read(cx).project_group_key(cx);
1126
1127        if self.workspaces.len() <= 1 {
1128            let has_worktrees = workspace.read(cx).visible_worktrees(cx).next().is_some();
1129
1130            if !has_worktrees {
1131                return false;
1132            }
1133
1134            let old_workspace = workspace.clone();
1135            let old_entity_id = old_workspace.entity_id();
1136
1137            let app_state = old_workspace.read(cx).app_state().clone();
1138
1139            let project = Project::local(
1140                app_state.client.clone(),
1141                app_state.node_runtime.clone(),
1142                app_state.user_store.clone(),
1143                app_state.languages.clone(),
1144                app_state.fs.clone(),
1145                None,
1146                project::LocalProjectFlags::default(),
1147                cx,
1148            );
1149
1150            let new_workspace = cx.new(|cx| Workspace::new(None, project, app_state, window, cx));
1151
1152            self.workspaces[0] = new_workspace.clone();
1153            self.active_workspace = ActiveWorkspace::Persistent(0);
1154
1155            Self::subscribe_to_workspace(&new_workspace, window, cx);
1156
1157            self.sync_sidebar_to_workspace(&new_workspace, cx);
1158
1159            let weak_self = cx.weak_entity();
1160
1161            new_workspace.update(cx, |workspace, cx| {
1162                workspace.set_multi_workspace(weak_self, cx);
1163            });
1164
1165            self.detach_workspace(&old_workspace, cx);
1166
1167            cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(old_entity_id));
1168            cx.emit(MultiWorkspaceEvent::WorkspaceAdded(new_workspace));
1169            cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
1170        } else {
1171            let removed_workspace = self.workspaces.remove(index);
1172
1173            if let Some(active_index) = self.active_workspace.persistent_index() {
1174                if active_index >= self.workspaces.len() {
1175                    self.active_workspace = ActiveWorkspace::Persistent(self.workspaces.len() - 1);
1176                } else if active_index > index {
1177                    self.active_workspace = ActiveWorkspace::Persistent(active_index - 1);
1178                }
1179            }
1180
1181            self.detach_workspace(&removed_workspace, cx);
1182
1183            cx.emit(MultiWorkspaceEvent::WorkspaceRemoved(
1184                removed_workspace.entity_id(),
1185            ));
1186            cx.emit(MultiWorkspaceEvent::ActiveWorkspaceChanged);
1187        }
1188
1189        let key_still_in_use = self
1190            .workspaces
1191            .iter()
1192            .any(|ws| ws.read(cx).project_group_key(cx) == old_key);
1193
1194        if !key_still_in_use {
1195            self.project_group_keys.retain(|k| k != &old_key);
1196        }
1197
1198        self.serialize(cx);
1199        self.focus_active_workspace(window, cx);
1200        cx.notify();
1201
1202        true
1203    }
1204
1205    pub fn move_workspace_to_new_window(
1206        &mut self,
1207        workspace: &Entity<Workspace>,
1208        window: &mut Window,
1209        cx: &mut Context<Self>,
1210    ) {
1211        let workspace = workspace.clone();
1212        if !self.remove(&workspace, window, cx) {
1213            return;
1214        }
1215
1216        let app_state: Arc<AppState> = workspace.read(cx).app_state().clone();
1217
1218        cx.defer(move |cx| {
1219            let options = (app_state.build_window_options)(None, cx);
1220
1221            let Ok(window) = cx.open_window(options, |window, cx| {
1222                cx.new(|cx| MultiWorkspace::new(workspace, window, cx))
1223            }) else {
1224                return;
1225            };
1226
1227            let _ = window.update(cx, |_, window, _| {
1228                window.activate_window();
1229            });
1230        });
1231    }
1232
1233    pub fn move_project_group_to_new_window(
1234        &mut self,
1235        key: &ProjectGroupKey,
1236        window: &mut Window,
1237        cx: &mut Context<Self>,
1238    ) {
1239        let workspaces: Vec<_> = self
1240            .workspaces_for_project_group(key, cx)
1241            .cloned()
1242            .collect();
1243        if workspaces.is_empty() {
1244            return;
1245        }
1246
1247        self.project_group_keys.retain(|k| k != key);
1248
1249        let mut removed = Vec::new();
1250        for workspace in &workspaces {
1251            if self.remove(workspace, window, cx) {
1252                removed.push(workspace.clone());
1253            }
1254        }
1255
1256        if removed.is_empty() {
1257            return;
1258        }
1259
1260        let app_state = removed[0].read(cx).app_state().clone();
1261
1262        cx.defer(move |cx| {
1263            let options = (app_state.build_window_options)(None, cx);
1264
1265            let first = removed[0].clone();
1266            let rest = removed[1..].to_vec();
1267
1268            let Ok(new_window) = cx.open_window(options, |window, cx| {
1269                cx.new(|cx| MultiWorkspace::new(first, window, cx))
1270            }) else {
1271                return;
1272            };
1273
1274            new_window
1275                .update(cx, |mw, window, cx| {
1276                    for workspace in rest {
1277                        mw.activate(workspace, window, cx);
1278                    }
1279                    window.activate_window();
1280                })
1281                .log_err();
1282        });
1283    }
1284
1285    fn move_active_workspace_to_new_window(
1286        &mut self,
1287        _: &MoveWorkspaceToNewWindow,
1288        window: &mut Window,
1289        cx: &mut Context<Self>,
1290    ) {
1291        let workspace = self.workspace().clone();
1292        self.move_workspace_to_new_window(&workspace, window, cx);
1293    }
1294
1295    pub fn open_project(
1296        &mut self,
1297        paths: Vec<PathBuf>,
1298        open_mode: OpenMode,
1299        window: &mut Window,
1300        cx: &mut Context<Self>,
1301    ) -> Task<Result<Entity<Workspace>>> {
1302        if self.multi_workspace_enabled(cx) {
1303            self.find_or_create_local_workspace(PathList::new(&paths), window, cx)
1304        } else {
1305            let workspace = self.workspace().clone();
1306            cx.spawn_in(window, async move |_this, cx| {
1307                let should_continue = workspace
1308                    .update_in(cx, |workspace, window, cx| {
1309                        workspace.prepare_to_close(crate::CloseIntent::ReplaceWindow, window, cx)
1310                    })?
1311                    .await?;
1312                if should_continue {
1313                    workspace
1314                        .update_in(cx, |workspace, window, cx| {
1315                            workspace.open_workspace_for_paths(open_mode, paths, window, cx)
1316                        })?
1317                        .await
1318                } else {
1319                    Ok(workspace)
1320                }
1321            })
1322        }
1323    }
1324}
1325
1326impl Render for MultiWorkspace {
1327    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1328        let multi_workspace_enabled = self.multi_workspace_enabled(cx);
1329        let sidebar_side = self.sidebar_side(cx);
1330        let sidebar_on_right = sidebar_side == SidebarSide::Right;
1331
1332        let sidebar: Option<AnyElement> = if multi_workspace_enabled && self.sidebar_open() {
1333            self.sidebar.as_ref().map(|sidebar_handle| {
1334                let weak = cx.weak_entity();
1335
1336                let sidebar_width = sidebar_handle.width(cx);
1337                let resize_handle = deferred(
1338                    div()
1339                        .id("sidebar-resize-handle")
1340                        .absolute()
1341                        .when(!sidebar_on_right, |el| {
1342                            el.right(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
1343                        })
1344                        .when(sidebar_on_right, |el| {
1345                            el.left(-SIDEBAR_RESIZE_HANDLE_SIZE / 2.)
1346                        })
1347                        .top(px(0.))
1348                        .h_full()
1349                        .w(SIDEBAR_RESIZE_HANDLE_SIZE)
1350                        .cursor_col_resize()
1351                        .on_drag(DraggedSidebar, |dragged, _, _, cx| {
1352                            cx.stop_propagation();
1353                            cx.new(|_| dragged.clone())
1354                        })
1355                        .on_mouse_down(MouseButton::Left, |_, _, cx| {
1356                            cx.stop_propagation();
1357                        })
1358                        .on_mouse_up(MouseButton::Left, move |event, _, cx| {
1359                            if event.click_count == 2 {
1360                                weak.update(cx, |this, cx| {
1361                                    if let Some(sidebar) = this.sidebar.as_mut() {
1362                                        sidebar.set_width(None, cx);
1363                                    }
1364                                    this.serialize(cx);
1365                                })
1366                                .ok();
1367                                cx.stop_propagation();
1368                            } else {
1369                                weak.update(cx, |this, cx| {
1370                                    this.serialize(cx);
1371                                })
1372                                .ok();
1373                            }
1374                        })
1375                        .occlude(),
1376                );
1377
1378                div()
1379                    .id("sidebar-container")
1380                    .relative()
1381                    .h_full()
1382                    .w(sidebar_width)
1383                    .flex_shrink_0()
1384                    .child(sidebar_handle.to_any())
1385                    .child(resize_handle)
1386                    .into_any_element()
1387            })
1388        } else {
1389            None
1390        };
1391
1392        let (left_sidebar, right_sidebar) = if sidebar_on_right {
1393            (None, sidebar)
1394        } else {
1395            (sidebar, None)
1396        };
1397
1398        let ui_font = theme_settings::setup_ui_font(window, cx);
1399        let text_color = cx.theme().colors().text;
1400
1401        let workspace = self.workspace().clone();
1402        let workspace_key_context = workspace.update(cx, |workspace, cx| workspace.key_context(cx));
1403        let root = workspace.update(cx, |workspace, cx| workspace.actions(h_flex(), window, cx));
1404
1405        client_side_decorations(
1406            root.key_context(workspace_key_context)
1407                .relative()
1408                .size_full()
1409                .font(ui_font)
1410                .text_color(text_color)
1411                .on_action(cx.listener(Self::close_window))
1412                .when(self.multi_workspace_enabled(cx), |this| {
1413                    this.on_action(cx.listener(
1414                        |this: &mut Self, _: &ToggleWorkspaceSidebar, window, cx| {
1415                            this.toggle_sidebar(window, cx);
1416                        },
1417                    ))
1418                    .on_action(cx.listener(
1419                        |this: &mut Self, _: &CloseWorkspaceSidebar, window, cx| {
1420                            this.close_sidebar_action(window, cx);
1421                        },
1422                    ))
1423                    .on_action(cx.listener(
1424                        |this: &mut Self, _: &FocusWorkspaceSidebar, window, cx| {
1425                            this.focus_sidebar(window, cx);
1426                        },
1427                    ))
1428                    .on_action(cx.listener(Self::move_active_workspace_to_new_window))
1429                    .on_action(cx.listener(
1430                        |this: &mut Self, action: &ToggleThreadSwitcher, window, cx| {
1431                            if let Some(sidebar) = &this.sidebar {
1432                                sidebar.toggle_thread_switcher(action.select_last, window, cx);
1433                            }
1434                        },
1435                    ))
1436                })
1437                .when(
1438                    self.sidebar_open() && self.multi_workspace_enabled(cx),
1439                    |this| {
1440                        this.on_drag_move(cx.listener(
1441                            move |this: &mut Self,
1442                                  e: &DragMoveEvent<DraggedSidebar>,
1443                                  window,
1444                                  cx| {
1445                                if let Some(sidebar) = &this.sidebar {
1446                                    let new_width = if sidebar_on_right {
1447                                        window.bounds().size.width - e.event.position.x
1448                                    } else {
1449                                        e.event.position.x
1450                                    };
1451                                    sidebar.set_width(Some(new_width), cx);
1452                                }
1453                            },
1454                        ))
1455                    },
1456                )
1457                .children(left_sidebar)
1458                .child(
1459                    div()
1460                        .flex()
1461                        .flex_1()
1462                        .size_full()
1463                        .overflow_hidden()
1464                        .child(self.workspace().clone()),
1465                )
1466                .children(right_sidebar)
1467                .child(self.workspace().read(cx).modal_layer.clone())
1468                .children(self.sidebar_overlay.as_ref().map(|view| {
1469                    deferred(div().absolute().size_full().inset_0().occlude().child(
1470                        v_flex().h(px(0.0)).top_20().items_center().child(
1471                            h_flex().occlude().child(view.clone()).on_mouse_down(
1472                                MouseButton::Left,
1473                                |_, _, cx| {
1474                                    cx.stop_propagation();
1475                                },
1476                            ),
1477                        ),
1478                    ))
1479                    .with_priority(2)
1480                })),
1481            window,
1482            cx,
1483            Tiling {
1484                left: !sidebar_on_right && multi_workspace_enabled && self.sidebar_open(),
1485                right: sidebar_on_right && multi_workspace_enabled && self.sidebar_open(),
1486                ..Tiling::default()
1487            },
1488        )
1489    }
1490}