workspace.rs

   1pub mod dock;
   2pub mod item;
   3pub mod notifications;
   4pub mod pane;
   5pub mod pane_group;
   6mod persistence;
   7pub mod searchable;
   8pub mod shared_screen;
   9mod status_bar;
  10mod toolbar;
  11mod workspace_settings;
  12
  13use anyhow::{anyhow, Context, Result};
  14use call::ActiveCall;
  15use client::{
  16    proto::{self, PeerId},
  17    Client, TypedEnvelope, UserStore,
  18};
  19use collections::{hash_map, HashMap, HashSet};
  20use drag_and_drop::DragAndDrop;
  21use futures::{
  22    channel::{mpsc, oneshot},
  23    future::try_join_all,
  24    FutureExt, StreamExt,
  25};
  26use gpui::{
  27    actions,
  28    elements::*,
  29    geometry::{
  30        rect::RectF,
  31        vector::{vec2f, Vector2F},
  32    },
  33    impl_actions,
  34    platform::{
  35        CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
  36        WindowOptions,
  37    },
  38    AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
  39    ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext, ViewHandle,
  40    WeakViewHandle, WindowContext,
  41};
  42use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
  43use itertools::Itertools;
  44use language::{LanguageRegistry, Rope};
  45use std::{
  46    any::TypeId,
  47    borrow::Cow,
  48    cmp, env,
  49    future::Future,
  50    path::{Path, PathBuf},
  51    rc::Rc,
  52    str,
  53    sync::{atomic::AtomicUsize, Arc},
  54    time::Duration,
  55};
  56
  57use crate::{
  58    notifications::{simple_message_notification::MessageNotification, NotificationTracker},
  59    persistence::model::{
  60        DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
  61    },
  62};
  63use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
  64use lazy_static::lazy_static;
  65use notifications::{NotificationHandle, NotifyResultExt};
  66pub use pane::*;
  67pub use pane_group::*;
  68use persistence::{model::SerializedItem, DB};
  69pub use persistence::{
  70    model::{ItemId, WorkspaceLocation},
  71    WorkspaceDb, DB as WORKSPACE_DB,
  72};
  73use postage::prelude::Stream;
  74use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
  75use serde::Deserialize;
  76use shared_screen::SharedScreen;
  77use status_bar::StatusBar;
  78pub use status_bar::StatusItemView;
  79use theme::{Theme, ThemeSettings};
  80pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
  81use util::{async_iife, ResultExt};
  82pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
  83
  84lazy_static! {
  85    static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
  86        .ok()
  87        .as_deref()
  88        .and_then(parse_pixel_position_env_var);
  89    static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
  90        .ok()
  91        .as_deref()
  92        .and_then(parse_pixel_position_env_var);
  93}
  94
  95pub trait Modal: View {
  96    fn has_focus(&self) -> bool;
  97    fn dismiss_on_event(event: &Self::Event) -> bool;
  98}
  99
 100trait ModalHandle {
 101    fn as_any(&self) -> &AnyViewHandle;
 102    fn has_focus(&self, cx: &WindowContext) -> bool;
 103}
 104
 105impl<T: Modal> ModalHandle for ViewHandle<T> {
 106    fn as_any(&self) -> &AnyViewHandle {
 107        self
 108    }
 109
 110    fn has_focus(&self, cx: &WindowContext) -> bool {
 111        self.read(cx).has_focus()
 112    }
 113}
 114
 115#[derive(Clone, PartialEq)]
 116pub struct RemoveWorktreeFromProject(pub WorktreeId);
 117
 118actions!(
 119    workspace,
 120    [
 121        Open,
 122        NewFile,
 123        NewWindow,
 124        CloseWindow,
 125        AddFolderToProject,
 126        Unfollow,
 127        Save,
 128        SaveAs,
 129        SaveAll,
 130        ActivatePreviousPane,
 131        ActivateNextPane,
 132        FollowNextCollaborator,
 133        NewTerminal,
 134        NewCenterTerminal,
 135        ToggleTerminalFocus,
 136        NewSearch,
 137        Feedback,
 138        Restart,
 139        Welcome,
 140        ToggleZoom,
 141        ToggleLeftDock,
 142        ToggleRightDock,
 143        ToggleBottomDock,
 144    ]
 145);
 146
 147#[derive(Clone, PartialEq)]
 148pub struct OpenPaths {
 149    pub paths: Vec<PathBuf>,
 150}
 151
 152#[derive(Clone, Deserialize, PartialEq)]
 153pub struct ActivatePane(pub usize);
 154
 155#[derive(Deserialize)]
 156pub struct Toast {
 157    id: usize,
 158    msg: Cow<'static, str>,
 159    #[serde(skip)]
 160    on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
 161}
 162
 163impl Toast {
 164    pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
 165        Toast {
 166            id,
 167            msg: msg.into(),
 168            on_click: None,
 169        }
 170    }
 171
 172    pub fn on_click<F, M>(mut self, message: M, on_click: F) -> Self
 173    where
 174        M: Into<Cow<'static, str>>,
 175        F: Fn(&mut WindowContext) + 'static,
 176    {
 177        self.on_click = Some((message.into(), Arc::new(on_click)));
 178        self
 179    }
 180}
 181
 182impl PartialEq for Toast {
 183    fn eq(&self, other: &Self) -> bool {
 184        self.id == other.id
 185            && self.msg == other.msg
 186            && self.on_click.is_some() == other.on_click.is_some()
 187    }
 188}
 189
 190impl Clone for Toast {
 191    fn clone(&self) -> Self {
 192        Toast {
 193            id: self.id,
 194            msg: self.msg.to_owned(),
 195            on_click: self.on_click.clone(),
 196        }
 197    }
 198}
 199
 200impl_actions!(workspace, [ActivatePane, Toast]);
 201
 202pub type WorkspaceId = i64;
 203
 204pub fn init_settings(cx: &mut AppContext) {
 205    settings::register::<WorkspaceSettings>(cx);
 206    settings::register::<item::ItemSettings>(cx);
 207}
 208
 209pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
 210    init_settings(cx);
 211    pane::init(cx);
 212    notifications::init(cx);
 213
 214    cx.add_global_action({
 215        let app_state = Arc::downgrade(&app_state);
 216        move |_: &Open, cx: &mut AppContext| {
 217            let mut paths = cx.prompt_for_paths(PathPromptOptions {
 218                files: true,
 219                directories: true,
 220                multiple: true,
 221            });
 222
 223            if let Some(app_state) = app_state.upgrade() {
 224                cx.spawn(move |mut cx| async move {
 225                    if let Some(paths) = paths.recv().await.flatten() {
 226                        cx.update(|cx| {
 227                            open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
 228                        });
 229                    }
 230                })
 231                .detach();
 232            }
 233        }
 234    });
 235    cx.add_async_action(Workspace::open);
 236
 237    cx.add_async_action(Workspace::follow_next_collaborator);
 238    cx.add_async_action(Workspace::close);
 239    cx.add_global_action(Workspace::close_global);
 240    cx.add_global_action(restart);
 241    cx.add_async_action(Workspace::save_all);
 242    cx.add_action(Workspace::add_folder_to_project);
 243    cx.add_action(
 244        |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
 245            let pane = workspace.active_pane().clone();
 246            workspace.unfollow(&pane, cx);
 247        },
 248    );
 249    cx.add_action(
 250        |workspace: &mut Workspace, _: &Save, cx: &mut ViewContext<Workspace>| {
 251            workspace.save_active_item(false, cx).detach_and_log_err(cx);
 252        },
 253    );
 254    cx.add_action(
 255        |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
 256            workspace.save_active_item(true, cx).detach_and_log_err(cx);
 257        },
 258    );
 259    cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
 260        workspace.activate_previous_pane(cx)
 261    });
 262    cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
 263        workspace.activate_next_pane(cx)
 264    });
 265    cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
 266        workspace.toggle_dock(DockPosition::Left, cx);
 267    });
 268    cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
 269        workspace.toggle_dock(DockPosition::Right, cx);
 270    });
 271    cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
 272        workspace.toggle_dock(DockPosition::Bottom, cx);
 273    });
 274    cx.add_action(Workspace::activate_pane_at_index);
 275    cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
 276        workspace.reopen_closed_item(cx).detach();
 277    });
 278    cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
 279        workspace
 280            .go_back(workspace.active_pane().downgrade(), cx)
 281            .detach();
 282    });
 283    cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
 284        workspace
 285            .go_forward(workspace.active_pane().downgrade(), cx)
 286            .detach();
 287    });
 288
 289    cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
 290        cx.spawn(|workspace, mut cx| async move {
 291            let err = install_cli::install_cli(&cx)
 292                .await
 293                .context("Failed to create CLI symlink");
 294
 295            workspace.update(&mut cx, |workspace, cx| {
 296                if matches!(err, Err(_)) {
 297                    err.notify_err(workspace, cx);
 298                } else {
 299                    workspace.show_notification(1, cx, |cx| {
 300                        cx.add_view(|_| {
 301                            MessageNotification::new("Successfully installed the `zed` binary")
 302                        })
 303                    });
 304                }
 305            })
 306        })
 307        .detach();
 308    });
 309
 310    let client = &app_state.client;
 311    client.add_view_request_handler(Workspace::handle_follow);
 312    client.add_view_message_handler(Workspace::handle_unfollow);
 313    client.add_view_message_handler(Workspace::handle_update_followers);
 314}
 315
 316type ProjectItemBuilders = HashMap<
 317    TypeId,
 318    fn(ModelHandle<Project>, AnyModelHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
 319>;
 320pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
 321    cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
 322        builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
 323            let item = model.downcast::<I::Item>().unwrap();
 324            Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
 325        });
 326    });
 327}
 328
 329type FollowableItemBuilder = fn(
 330    ViewHandle<Pane>,
 331    ModelHandle<Project>,
 332    ViewId,
 333    &mut Option<proto::view::Variant>,
 334    &mut AppContext,
 335) -> Option<Task<Result<Box<dyn FollowableItemHandle>>>>;
 336type FollowableItemBuilders = HashMap<
 337    TypeId,
 338    (
 339        FollowableItemBuilder,
 340        fn(&AnyViewHandle) -> Box<dyn FollowableItemHandle>,
 341    ),
 342>;
 343pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
 344    cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
 345        builders.insert(
 346            TypeId::of::<I>(),
 347            (
 348                |pane, project, id, state, cx| {
 349                    I::from_state_proto(pane, project, id, state, cx).map(|task| {
 350                        cx.foreground()
 351                            .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
 352                    })
 353                },
 354                |this| Box::new(this.clone().downcast::<I>().unwrap()),
 355            ),
 356        );
 357    });
 358}
 359
 360type ItemDeserializers = HashMap<
 361    Arc<str>,
 362    fn(
 363        ModelHandle<Project>,
 364        WeakViewHandle<Workspace>,
 365        WorkspaceId,
 366        ItemId,
 367        &mut ViewContext<Pane>,
 368    ) -> Task<Result<Box<dyn ItemHandle>>>,
 369>;
 370pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 371    cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
 372        if let Some(serialized_item_kind) = I::serialized_item_kind() {
 373            deserializers.insert(
 374                Arc::from(serialized_item_kind),
 375                |project, workspace, workspace_id, item_id, cx| {
 376                    let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
 377                    cx.foreground()
 378                        .spawn(async { Ok(Box::new(task.await?) as Box<_>) })
 379                },
 380            );
 381        }
 382    });
 383}
 384
 385pub struct AppState {
 386    pub languages: Arc<LanguageRegistry>,
 387    pub client: Arc<client::Client>,
 388    pub user_store: ModelHandle<client::UserStore>,
 389    pub fs: Arc<dyn fs::Fs>,
 390    pub build_window_options:
 391        fn(Option<WindowBounds>, Option<uuid::Uuid>, &dyn Platform) -> WindowOptions<'static>,
 392    pub initialize_workspace:
 393        fn(WeakViewHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<Result<()>>,
 394    pub background_actions: BackgroundActions,
 395}
 396
 397impl AppState {
 398    #[cfg(any(test, feature = "test-support"))]
 399    pub fn test(cx: &mut AppContext) -> Arc<Self> {
 400        use settings::SettingsStore;
 401
 402        if !cx.has_global::<SettingsStore>() {
 403            cx.set_global(SettingsStore::test(cx));
 404        }
 405
 406        let fs = fs::FakeFs::new(cx.background().clone());
 407        let languages = Arc::new(LanguageRegistry::test());
 408        let http_client = util::http::FakeHttpClient::with_404_response();
 409        let client = Client::new(http_client.clone(), cx);
 410        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
 411
 412        theme::init((), cx);
 413        client::init(&client, cx);
 414        crate::init_settings(cx);
 415
 416        Arc::new(Self {
 417            client,
 418            fs,
 419            languages,
 420            user_store,
 421            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
 422            build_window_options: |_, _, _| Default::default(),
 423            background_actions: || &[],
 424        })
 425    }
 426}
 427
 428struct DelayedDebouncedEditAction {
 429    task: Option<Task<()>>,
 430    cancel_channel: Option<oneshot::Sender<()>>,
 431}
 432
 433impl DelayedDebouncedEditAction {
 434    fn new() -> DelayedDebouncedEditAction {
 435        DelayedDebouncedEditAction {
 436            task: None,
 437            cancel_channel: None,
 438        }
 439    }
 440
 441    fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
 442    where
 443        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
 444    {
 445        if let Some(channel) = self.cancel_channel.take() {
 446            _ = channel.send(());
 447        }
 448
 449        let (sender, mut receiver) = oneshot::channel::<()>();
 450        self.cancel_channel = Some(sender);
 451
 452        let previous_task = self.task.take();
 453        self.task = Some(cx.spawn(|workspace, mut cx| async move {
 454            let mut timer = cx.background().timer(delay).fuse();
 455            if let Some(previous_task) = previous_task {
 456                previous_task.await;
 457            }
 458
 459            futures::select_biased! {
 460                _ = receiver => return,
 461                    _ = timer => {}
 462            }
 463
 464            if let Some(result) = workspace
 465                .update(&mut cx, |workspace, cx| (func)(workspace, cx))
 466                .log_err()
 467            {
 468                result.await.log_err();
 469            }
 470        }));
 471    }
 472}
 473
 474pub enum Event {
 475    PaneAdded(ViewHandle<Pane>),
 476    ContactRequestedJoin(u64),
 477}
 478
 479pub struct Workspace {
 480    weak_self: WeakViewHandle<Self>,
 481    remote_entity_subscription: Option<client::Subscription>,
 482    modal: Option<ActiveModal>,
 483    zoomed: Option<AnyWeakViewHandle>,
 484    zoomed_position: Option<DockPosition>,
 485    center: PaneGroup,
 486    left_dock: ViewHandle<Dock>,
 487    bottom_dock: ViewHandle<Dock>,
 488    right_dock: ViewHandle<Dock>,
 489    panes: Vec<ViewHandle<Pane>>,
 490    panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
 491    active_pane: ViewHandle<Pane>,
 492    last_active_center_pane: Option<WeakViewHandle<Pane>>,
 493    status_bar: ViewHandle<StatusBar>,
 494    titlebar_item: Option<AnyViewHandle>,
 495    notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
 496    project: ModelHandle<Project>,
 497    leader_state: LeaderState,
 498    follower_states_by_leader: FollowerStatesByLeader,
 499    last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
 500    window_edited: bool,
 501    active_call: Option<(ModelHandle<ActiveCall>, Vec<gpui::Subscription>)>,
 502    leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
 503    database_id: WorkspaceId,
 504    app_state: Arc<AppState>,
 505    subscriptions: Vec<Subscription>,
 506    _apply_leader_updates: Task<Result<()>>,
 507    _observe_current_user: Task<Result<()>>,
 508    _schedule_serialize: Option<Task<()>>,
 509    pane_history_timestamp: Arc<AtomicUsize>,
 510}
 511
 512struct ActiveModal {
 513    view: Box<dyn ModalHandle>,
 514    previously_focused_view_id: Option<usize>,
 515}
 516
 517#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
 518pub struct ViewId {
 519    pub creator: PeerId,
 520    pub id: u64,
 521}
 522
 523#[derive(Default)]
 524struct LeaderState {
 525    followers: HashSet<PeerId>,
 526}
 527
 528type FollowerStatesByLeader = HashMap<PeerId, HashMap<ViewHandle<Pane>, FollowerState>>;
 529
 530#[derive(Default)]
 531struct FollowerState {
 532    active_view_id: Option<ViewId>,
 533    items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
 534}
 535
 536impl Workspace {
 537    pub fn new(
 538        workspace_id: WorkspaceId,
 539        project: ModelHandle<Project>,
 540        app_state: Arc<AppState>,
 541        cx: &mut ViewContext<Self>,
 542    ) -> Self {
 543        cx.observe(&project, |_, _, cx| cx.notify()).detach();
 544        cx.subscribe(&project, move |this, _, event, cx| {
 545            match event {
 546                project::Event::RemoteIdChanged(remote_id) => {
 547                    this.update_window_title(cx);
 548                    this.project_remote_id_changed(*remote_id, cx);
 549                }
 550
 551                project::Event::CollaboratorLeft(peer_id) => {
 552                    this.collaborator_left(*peer_id, cx);
 553                }
 554
 555                project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
 556                    this.update_window_title(cx);
 557                    this.serialize_workspace(cx);
 558                }
 559
 560                project::Event::DisconnectedFromHost => {
 561                    this.update_window_edited(cx);
 562                    cx.blur();
 563                }
 564
 565                project::Event::Closed => {
 566                    cx.remove_window();
 567                }
 568
 569                project::Event::DeletedEntry(entry_id) => {
 570                    for pane in this.panes.iter() {
 571                        pane.update(cx, |pane, cx| {
 572                            pane.handle_deleted_project_item(*entry_id, cx)
 573                        });
 574                    }
 575                }
 576
 577                project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
 578                    cx.add_view(|_| MessageNotification::new(message.clone()))
 579                }),
 580
 581                _ => {}
 582            }
 583            cx.notify()
 584        })
 585        .detach();
 586
 587        let weak_handle = cx.weak_handle();
 588        let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
 589
 590        let center_pane = cx.add_view(|cx| {
 591            Pane::new(
 592                weak_handle.clone(),
 593                project.clone(),
 594                app_state.background_actions,
 595                pane_history_timestamp.clone(),
 596                cx,
 597            )
 598        });
 599        cx.subscribe(&center_pane, Self::handle_pane_event).detach();
 600        cx.focus(&center_pane);
 601        cx.emit(Event::PaneAdded(center_pane.clone()));
 602
 603        let mut current_user = app_state.user_store.read(cx).watch_current_user();
 604        let mut connection_status = app_state.client.status();
 605        let _observe_current_user = cx.spawn(|this, mut cx| async move {
 606            current_user.recv().await;
 607            connection_status.recv().await;
 608            let mut stream =
 609                Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 610
 611            while stream.recv().await.is_some() {
 612                this.update(&mut cx, |_, cx| cx.notify())?;
 613            }
 614            anyhow::Ok(())
 615        });
 616
 617        // All leader updates are enqueued and then processed in a single task, so
 618        // that each asynchronous operation can be run in order.
 619        let (leader_updates_tx, mut leader_updates_rx) =
 620            mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
 621        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
 622            while let Some((leader_id, update)) = leader_updates_rx.next().await {
 623                Self::process_leader_update(&this, leader_id, update, &mut cx)
 624                    .await
 625                    .log_err();
 626            }
 627
 628            Ok(())
 629        });
 630
 631        cx.emit_global(WorkspaceCreated(weak_handle.clone()));
 632
 633        let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
 634        let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
 635        let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
 636        let left_dock_buttons =
 637            cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
 638        let bottom_dock_buttons =
 639            cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
 640        let right_dock_buttons =
 641            cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
 642        let status_bar = cx.add_view(|cx| {
 643            let mut status_bar = StatusBar::new(&center_pane.clone(), cx);
 644            status_bar.add_left_item(left_dock_buttons, cx);
 645            status_bar.add_right_item(right_dock_buttons, cx);
 646            status_bar.add_right_item(bottom_dock_buttons, cx);
 647            status_bar
 648        });
 649
 650        cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
 651            drag_and_drop.register_container(weak_handle.clone());
 652        });
 653
 654        let mut active_call = None;
 655        if cx.has_global::<ModelHandle<ActiveCall>>() {
 656            let call = cx.global::<ModelHandle<ActiveCall>>().clone();
 657            let mut subscriptions = Vec::new();
 658            subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
 659            active_call = Some((call, subscriptions));
 660        }
 661
 662        let subscriptions = vec![
 663            cx.observe_fullscreen(|_, _, cx| cx.notify()),
 664            cx.observe_window_activation(Self::on_window_activation_changed),
 665            cx.observe_window_bounds(move |_, mut bounds, display, cx| {
 666                // Transform fixed bounds to be stored in terms of the containing display
 667                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 668                    if let Some(screen) = cx.platform().screen_by_id(display) {
 669                        let screen_bounds = screen.bounds();
 670                        window_bounds
 671                            .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
 672                        window_bounds
 673                            .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
 674                        bounds = WindowBounds::Fixed(window_bounds);
 675                    }
 676                }
 677
 678                cx.background()
 679                    .spawn(DB.set_window_bounds(workspace_id, bounds, display))
 680                    .detach_and_log_err(cx);
 681            }),
 682            cx.observe(&left_dock, |this, _, cx| {
 683                this.serialize_workspace(cx);
 684                cx.notify();
 685            }),
 686            cx.observe(&bottom_dock, |this, _, cx| {
 687                this.serialize_workspace(cx);
 688                cx.notify();
 689            }),
 690            cx.observe(&right_dock, |this, _, cx| {
 691                this.serialize_workspace(cx);
 692                cx.notify();
 693            }),
 694        ];
 695
 696        let mut this = Workspace {
 697            weak_self: weak_handle.clone(),
 698            modal: None,
 699            zoomed: None,
 700            zoomed_position: None,
 701            center: PaneGroup::new(center_pane.clone()),
 702            panes: vec![center_pane.clone()],
 703            panes_by_item: Default::default(),
 704            active_pane: center_pane.clone(),
 705            last_active_center_pane: Some(center_pane.downgrade()),
 706            status_bar,
 707            titlebar_item: None,
 708            notifications: Default::default(),
 709            remote_entity_subscription: None,
 710            left_dock,
 711            bottom_dock,
 712            right_dock,
 713            project: project.clone(),
 714            leader_state: Default::default(),
 715            follower_states_by_leader: Default::default(),
 716            last_leaders_by_pane: Default::default(),
 717            window_edited: false,
 718            active_call,
 719            database_id: workspace_id,
 720            app_state,
 721            _observe_current_user,
 722            _apply_leader_updates,
 723            _schedule_serialize: None,
 724            leader_updates_tx,
 725            subscriptions,
 726            pane_history_timestamp,
 727        };
 728        this.project_remote_id_changed(project.read(cx).remote_id(), cx);
 729        cx.defer(|this, cx| this.update_window_title(cx));
 730        this
 731    }
 732
 733    fn new_local(
 734        abs_paths: Vec<PathBuf>,
 735        app_state: Arc<AppState>,
 736        requesting_window_id: Option<usize>,
 737        cx: &mut AppContext,
 738    ) -> Task<(
 739        WeakViewHandle<Workspace>,
 740        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
 741    )> {
 742        let project_handle = Project::local(
 743            app_state.client.clone(),
 744            app_state.user_store.clone(),
 745            app_state.languages.clone(),
 746            app_state.fs.clone(),
 747            cx,
 748        );
 749
 750        cx.spawn(|mut cx| async move {
 751            let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
 752
 753            let paths_to_open = Arc::new(abs_paths);
 754
 755            // Get project paths for all of the abs_paths
 756            let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
 757            let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
 758                Vec::with_capacity(paths_to_open.len());
 759            for path in paths_to_open.iter().cloned() {
 760                if let Some((worktree, project_entry)) = cx
 761                    .update(|cx| {
 762                        Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
 763                    })
 764                    .await
 765                    .log_err()
 766                {
 767                    worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
 768                    project_paths.push((path, Some(project_entry)));
 769                } else {
 770                    project_paths.push((path, None));
 771                }
 772            }
 773
 774            let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
 775                serialized_workspace.id
 776            } else {
 777                DB.next_id().await.unwrap_or(0)
 778            };
 779
 780            let workspace = requesting_window_id
 781                .and_then(|window_id| {
 782                    cx.update(|cx| {
 783                        cx.replace_root_view(window_id, |cx| {
 784                            Workspace::new(
 785                                workspace_id,
 786                                project_handle.clone(),
 787                                app_state.clone(),
 788                                cx,
 789                            )
 790                        })
 791                    })
 792                })
 793                .unwrap_or_else(|| {
 794                    let window_bounds_override = window_bounds_env_override(&cx);
 795                    let (bounds, display) = if let Some(bounds) = window_bounds_override {
 796                        (Some(bounds), None)
 797                    } else {
 798                        serialized_workspace
 799                            .as_ref()
 800                            .and_then(|serialized_workspace| {
 801                                let display = serialized_workspace.display?;
 802                                let mut bounds = serialized_workspace.bounds?;
 803
 804                                // Stored bounds are relative to the containing display.
 805                                // So convert back to global coordinates if that screen still exists
 806                                if let WindowBounds::Fixed(mut window_bounds) = bounds {
 807                                    if let Some(screen) = cx.platform().screen_by_id(display) {
 808                                        let screen_bounds = screen.bounds();
 809                                        window_bounds.set_origin_x(
 810                                            window_bounds.origin_x() + screen_bounds.origin_x(),
 811                                        );
 812                                        window_bounds.set_origin_y(
 813                                            window_bounds.origin_y() + screen_bounds.origin_y(),
 814                                        );
 815                                        bounds = WindowBounds::Fixed(window_bounds);
 816                                    } else {
 817                                        // Screen no longer exists. Return none here.
 818                                        return None;
 819                                    }
 820                                }
 821
 822                                Some((bounds, display))
 823                            })
 824                            .unzip()
 825                    };
 826
 827                    // Use the serialized workspace to construct the new window
 828                    cx.add_window(
 829                        (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
 830                        |cx| {
 831                            Workspace::new(
 832                                workspace_id,
 833                                project_handle.clone(),
 834                                app_state.clone(),
 835                                cx,
 836                            )
 837                        },
 838                    )
 839                    .1
 840                });
 841
 842            (app_state.initialize_workspace)(
 843                workspace.downgrade(),
 844                serialized_workspace.is_some(),
 845                app_state.clone(),
 846                cx.clone(),
 847            )
 848            .await
 849            .log_err();
 850
 851            cx.update_window(workspace.window_id(), |cx| cx.activate_window());
 852
 853            let workspace = workspace.downgrade();
 854            notify_if_database_failed(&workspace, &mut cx);
 855            let opened_items = open_items(
 856                serialized_workspace,
 857                &workspace,
 858                project_paths,
 859                app_state,
 860                cx,
 861            )
 862            .await;
 863
 864            (workspace, opened_items)
 865        })
 866    }
 867
 868    pub fn weak_handle(&self) -> WeakViewHandle<Self> {
 869        self.weak_self.clone()
 870    }
 871
 872    pub fn left_dock(&self) -> &ViewHandle<Dock> {
 873        &self.left_dock
 874    }
 875
 876    pub fn bottom_dock(&self) -> &ViewHandle<Dock> {
 877        &self.bottom_dock
 878    }
 879
 880    pub fn right_dock(&self) -> &ViewHandle<Dock> {
 881        &self.right_dock
 882    }
 883
 884    pub fn add_panel<T: Panel>(&mut self, panel: ViewHandle<T>, cx: &mut ViewContext<Self>)
 885    where
 886        T::Event: std::fmt::Debug,
 887    {
 888        let dock = match panel.position(cx) {
 889            DockPosition::Left => &self.left_dock,
 890            DockPosition::Bottom => &self.bottom_dock,
 891            DockPosition::Right => &self.right_dock,
 892        };
 893
 894        self.subscriptions.push(cx.subscribe(&panel, {
 895            let mut dock = dock.clone();
 896            let mut prev_position = panel.position(cx);
 897            move |this, panel, event, cx| {
 898                if T::should_change_position_on_event(event) {
 899                    let new_position = panel.read(cx).position(cx);
 900                    let mut was_visible = false;
 901                    dock.update(cx, |dock, cx| {
 902                        prev_position = new_position;
 903
 904                        was_visible = dock.is_open()
 905                            && dock
 906                                .visible_panel()
 907                                .map_or(false, |active_panel| active_panel.id() == panel.id());
 908                        dock.remove_panel(&panel, cx);
 909                    });
 910
 911                    if panel.is_zoomed(cx) {
 912                        this.zoomed_position = Some(new_position);
 913                    }
 914
 915                    dock = match panel.read(cx).position(cx) {
 916                        DockPosition::Left => &this.left_dock,
 917                        DockPosition::Bottom => &this.bottom_dock,
 918                        DockPosition::Right => &this.right_dock,
 919                    }
 920                    .clone();
 921                    dock.update(cx, |dock, cx| {
 922                        dock.add_panel(panel.clone(), cx);
 923                        if was_visible {
 924                            dock.set_open(true, cx);
 925                            dock.activate_panel(dock.panels_len() - 1, cx);
 926                        }
 927                    });
 928                } else if T::should_zoom_in_on_event(event) {
 929                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx));
 930                    if !panel.has_focus(cx) {
 931                        cx.focus(&panel);
 932                    }
 933                    this.zoomed = Some(panel.downgrade().into_any());
 934                    this.zoomed_position = Some(panel.read(cx).position(cx));
 935                } else if T::should_zoom_out_on_event(event) {
 936                    dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx));
 937                    if this.zoomed_position == Some(prev_position) {
 938                        this.zoomed = None;
 939                        this.zoomed_position = None;
 940                    }
 941                    cx.notify();
 942                } else if T::is_focus_event(event) {
 943                    let position = panel.read(cx).position(cx);
 944                    this.dismiss_zoomed_items_to_reveal(Some(position), cx);
 945                    if panel.is_zoomed(cx) {
 946                        this.zoomed = Some(panel.downgrade().into_any());
 947                        this.zoomed_position = Some(position);
 948                    } else {
 949                        this.zoomed = None;
 950                        this.zoomed_position = None;
 951                    }
 952                    this.update_active_view_for_followers(cx);
 953                    cx.notify();
 954                }
 955            }
 956        }));
 957
 958        dock.update(cx, |dock, cx| dock.add_panel(panel, cx));
 959    }
 960
 961    pub fn status_bar(&self) -> &ViewHandle<StatusBar> {
 962        &self.status_bar
 963    }
 964
 965    pub fn app_state(&self) -> &Arc<AppState> {
 966        &self.app_state
 967    }
 968
 969    pub fn user_store(&self) -> &ModelHandle<UserStore> {
 970        &self.app_state.user_store
 971    }
 972
 973    pub fn project(&self) -> &ModelHandle<Project> {
 974        &self.project
 975    }
 976
 977    pub fn recent_navigation_history(
 978        &self,
 979        limit: Option<usize>,
 980        cx: &AppContext,
 981    ) -> Vec<(ProjectPath, Option<PathBuf>)> {
 982        let mut abs_paths_opened: HashMap<PathBuf, HashSet<ProjectPath>> = HashMap::default();
 983        let mut history: HashMap<ProjectPath, (Option<PathBuf>, usize)> = HashMap::default();
 984        for pane in &self.panes {
 985            let pane = pane.read(cx);
 986            pane.nav_history()
 987                .for_each_entry(cx, |entry, (project_path, fs_path)| {
 988                    if let Some(fs_path) = &fs_path {
 989                        abs_paths_opened
 990                            .entry(fs_path.clone())
 991                            .or_default()
 992                            .insert(project_path.clone());
 993                    }
 994                    let timestamp = entry.timestamp;
 995                    match history.entry(project_path) {
 996                        hash_map::Entry::Occupied(mut entry) => {
 997                            let (_, old_timestamp) = entry.get();
 998                            if &timestamp > old_timestamp {
 999                                entry.insert((fs_path, timestamp));
1000                            }
1001                        }
1002                        hash_map::Entry::Vacant(entry) => {
1003                            entry.insert((fs_path, timestamp));
1004                        }
1005                    }
1006                });
1007        }
1008
1009        history
1010            .into_iter()
1011            .sorted_by_key(|(_, (_, timestamp))| *timestamp)
1012            .map(|(project_path, (fs_path, _))| (project_path, fs_path))
1013            .rev()
1014            .filter(|(history_path, abs_path)| {
1015                let latest_project_path_opened = abs_path
1016                    .as_ref()
1017                    .and_then(|abs_path| abs_paths_opened.get(abs_path))
1018                    .and_then(|project_paths| {
1019                        project_paths
1020                            .iter()
1021                            .max_by(|b1, b2| b1.worktree_id.cmp(&b2.worktree_id))
1022                    });
1023
1024                match latest_project_path_opened {
1025                    Some(latest_project_path_opened) => latest_project_path_opened == history_path,
1026                    None => true,
1027                }
1028            })
1029            .take(limit.unwrap_or(usize::MAX))
1030            .collect()
1031    }
1032
1033    fn navigate_history(
1034        &mut self,
1035        pane: WeakViewHandle<Pane>,
1036        mode: NavigationMode,
1037        cx: &mut ViewContext<Workspace>,
1038    ) -> Task<Result<()>> {
1039        let to_load = if let Some(pane) = pane.upgrade(cx) {
1040            cx.focus(&pane);
1041
1042            pane.update(cx, |pane, cx| {
1043                loop {
1044                    // Retrieve the weak item handle from the history.
1045                    let entry = pane.nav_history_mut().pop(mode, cx)?;
1046
1047                    // If the item is still present in this pane, then activate it.
1048                    if let Some(index) = entry
1049                        .item
1050                        .upgrade(cx)
1051                        .and_then(|v| pane.index_for_item(v.as_ref()))
1052                    {
1053                        let prev_active_item_index = pane.active_item_index();
1054                        pane.nav_history_mut().set_mode(mode);
1055                        pane.activate_item(index, true, true, cx);
1056                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1057
1058                        let mut navigated = prev_active_item_index != pane.active_item_index();
1059                        if let Some(data) = entry.data {
1060                            navigated |= pane.active_item()?.navigate(data, cx);
1061                        }
1062
1063                        if navigated {
1064                            break None;
1065                        }
1066                    }
1067                    // If the item is no longer present in this pane, then retrieve its
1068                    // project path in order to reopen it.
1069                    else {
1070                        break pane
1071                            .nav_history()
1072                            .path_for_item(entry.item.id())
1073                            .map(|(project_path, _)| (project_path, entry));
1074                    }
1075                }
1076            })
1077        } else {
1078            None
1079        };
1080
1081        if let Some((project_path, entry)) = to_load {
1082            // If the item was no longer present, then load it again from its previous path.
1083            let task = self.load_path(project_path, cx);
1084            cx.spawn(|workspace, mut cx| async move {
1085                let task = task.await;
1086                let mut navigated = false;
1087                if let Some((project_entry_id, build_item)) = task.log_err() {
1088                    let prev_active_item_id = pane.update(&mut cx, |pane, _| {
1089                        pane.nav_history_mut().set_mode(mode);
1090                        pane.active_item().map(|p| p.id())
1091                    })?;
1092
1093                    pane.update(&mut cx, |pane, cx| {
1094                        let item = pane.open_item(project_entry_id, true, cx, build_item);
1095                        navigated |= Some(item.id()) != prev_active_item_id;
1096                        pane.nav_history_mut().set_mode(NavigationMode::Normal);
1097                        if let Some(data) = entry.data {
1098                            navigated |= item.navigate(data, cx);
1099                        }
1100                    })?;
1101                }
1102
1103                if !navigated {
1104                    workspace
1105                        .update(&mut cx, |workspace, cx| {
1106                            Self::navigate_history(workspace, pane, mode, cx)
1107                        })?
1108                        .await?;
1109                }
1110
1111                Ok(())
1112            })
1113        } else {
1114            Task::ready(Ok(()))
1115        }
1116    }
1117
1118    pub fn go_back(
1119        &mut self,
1120        pane: WeakViewHandle<Pane>,
1121        cx: &mut ViewContext<Workspace>,
1122    ) -> Task<Result<()>> {
1123        self.navigate_history(pane, NavigationMode::GoingBack, cx)
1124    }
1125
1126    pub fn go_forward(
1127        &mut self,
1128        pane: WeakViewHandle<Pane>,
1129        cx: &mut ViewContext<Workspace>,
1130    ) -> Task<Result<()>> {
1131        self.navigate_history(pane, NavigationMode::GoingForward, cx)
1132    }
1133
1134    pub fn reopen_closed_item(&mut self, cx: &mut ViewContext<Workspace>) -> Task<Result<()>> {
1135        self.navigate_history(
1136            self.active_pane().downgrade(),
1137            NavigationMode::ReopeningClosedItem,
1138            cx,
1139        )
1140    }
1141
1142    pub fn client(&self) -> &Client {
1143        &self.app_state.client
1144    }
1145
1146    pub fn set_titlebar_item(&mut self, item: AnyViewHandle, cx: &mut ViewContext<Self>) {
1147        self.titlebar_item = Some(item);
1148        cx.notify();
1149    }
1150
1151    pub fn titlebar_item(&self) -> Option<AnyViewHandle> {
1152        self.titlebar_item.clone()
1153    }
1154
1155    /// Call the given callback with a workspace whose project is local.
1156    ///
1157    /// If the given workspace has a local project, then it will be passed
1158    /// to the callback. Otherwise, a new empty window will be created.
1159    pub fn with_local_workspace<T, F>(
1160        &mut self,
1161        cx: &mut ViewContext<Self>,
1162        callback: F,
1163    ) -> Task<Result<T>>
1164    where
1165        T: 'static,
1166        F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
1167    {
1168        if self.project.read(cx).is_local() {
1169            Task::Ready(Some(Ok(callback(self, cx))))
1170        } else {
1171            let task = Self::new_local(Vec::new(), self.app_state.clone(), None, cx);
1172            cx.spawn(|_vh, mut cx| async move {
1173                let (workspace, _) = task.await;
1174                workspace.update(&mut cx, callback)
1175            })
1176        }
1177    }
1178
1179    pub fn worktrees<'a>(
1180        &self,
1181        cx: &'a AppContext,
1182    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1183        self.project.read(cx).worktrees(cx)
1184    }
1185
1186    pub fn visible_worktrees<'a>(
1187        &self,
1188        cx: &'a AppContext,
1189    ) -> impl 'a + Iterator<Item = ModelHandle<Worktree>> {
1190        self.project.read(cx).visible_worktrees(cx)
1191    }
1192
1193    pub fn worktree_scans_complete(&self, cx: &AppContext) -> impl Future<Output = ()> + 'static {
1194        let futures = self
1195            .worktrees(cx)
1196            .filter_map(|worktree| worktree.read(cx).as_local())
1197            .map(|worktree| worktree.scan_complete())
1198            .collect::<Vec<_>>();
1199        async move {
1200            for future in futures {
1201                future.await;
1202            }
1203        }
1204    }
1205
1206    pub fn close_global(_: &CloseWindow, cx: &mut AppContext) {
1207        cx.spawn(|mut cx| async move {
1208            let id = cx
1209                .window_ids()
1210                .into_iter()
1211                .find(|&id| cx.window_is_active(id));
1212            if let Some(id) = id {
1213                //This can only get called when the window's project connection has been lost
1214                //so we don't need to prompt the user for anything and instead just close the window
1215                cx.remove_window(id);
1216            }
1217        })
1218        .detach();
1219    }
1220
1221    pub fn close(
1222        &mut self,
1223        _: &CloseWindow,
1224        cx: &mut ViewContext<Self>,
1225    ) -> Option<Task<Result<()>>> {
1226        let window_id = cx.window_id();
1227        let prepare = self.prepare_to_close(false, cx);
1228        Some(cx.spawn(|_, mut cx| async move {
1229            if prepare.await? {
1230                cx.remove_window(window_id);
1231            }
1232            Ok(())
1233        }))
1234    }
1235
1236    pub fn prepare_to_close(
1237        &mut self,
1238        quitting: bool,
1239        cx: &mut ViewContext<Self>,
1240    ) -> Task<Result<bool>> {
1241        let active_call = self.active_call().cloned();
1242        let window_id = cx.window_id();
1243
1244        cx.spawn(|this, mut cx| async move {
1245            let workspace_count = cx
1246                .window_ids()
1247                .into_iter()
1248                .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
1249                .count();
1250
1251            if let Some(active_call) = active_call {
1252                if !quitting
1253                    && workspace_count == 1
1254                    && active_call.read_with(&cx, |call, _| call.room().is_some())
1255                {
1256                    let answer = cx.prompt(
1257                        window_id,
1258                        PromptLevel::Warning,
1259                        "Do you want to leave the current call?",
1260                        &["Close window and hang up", "Cancel"],
1261                    );
1262
1263                    if let Some(mut answer) = answer {
1264                        if answer.next().await == Some(1) {
1265                            return anyhow::Ok(false);
1266                        } else {
1267                            active_call
1268                                .update(&mut cx, |call, cx| call.hang_up(cx))
1269                                .await
1270                                .log_err();
1271                        }
1272                    }
1273                }
1274            }
1275
1276            Ok(this
1277                .update(&mut cx, |this, cx| this.save_all_internal(true, cx))?
1278                .await?)
1279        })
1280    }
1281
1282    fn save_all(&mut self, _: &SaveAll, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1283        let save_all = self.save_all_internal(false, cx);
1284        Some(cx.foreground().spawn(async move {
1285            save_all.await?;
1286            Ok(())
1287        }))
1288    }
1289
1290    fn save_all_internal(
1291        &mut self,
1292        should_prompt_to_save: bool,
1293        cx: &mut ViewContext<Self>,
1294    ) -> Task<Result<bool>> {
1295        if self.project.read(cx).is_read_only() {
1296            return Task::ready(Ok(true));
1297        }
1298
1299        let dirty_items = self
1300            .panes
1301            .iter()
1302            .flat_map(|pane| {
1303                pane.read(cx).items().filter_map(|item| {
1304                    if item.is_dirty(cx) {
1305                        Some((pane.downgrade(), item.boxed_clone()))
1306                    } else {
1307                        None
1308                    }
1309                })
1310            })
1311            .collect::<Vec<_>>();
1312
1313        let project = self.project.clone();
1314        cx.spawn(|_, mut cx| async move {
1315            for (pane, item) in dirty_items {
1316                let (singleton, project_entry_ids) =
1317                    cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
1318                if singleton || !project_entry_ids.is_empty() {
1319                    if let Some(ix) =
1320                        pane.read_with(&cx, |pane, _| pane.index_for_item(item.as_ref()))?
1321                    {
1322                        if !Pane::save_item(
1323                            project.clone(),
1324                            &pane,
1325                            ix,
1326                            &*item,
1327                            should_prompt_to_save,
1328                            &mut cx,
1329                        )
1330                        .await?
1331                        {
1332                            return Ok(false);
1333                        }
1334                    }
1335                }
1336            }
1337            Ok(true)
1338        })
1339    }
1340
1341    pub fn open(&mut self, _: &Open, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1342        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1343            files: true,
1344            directories: true,
1345            multiple: true,
1346        });
1347
1348        Some(cx.spawn(|this, mut cx| async move {
1349            if let Some(paths) = paths.recv().await.flatten() {
1350                if let Some(task) = this
1351                    .update(&mut cx, |this, cx| this.open_workspace_for_paths(paths, cx))
1352                    .log_err()
1353                {
1354                    task.await?
1355                }
1356            }
1357            Ok(())
1358        }))
1359    }
1360
1361    pub fn open_workspace_for_paths(
1362        &mut self,
1363        paths: Vec<PathBuf>,
1364        cx: &mut ViewContext<Self>,
1365    ) -> Task<Result<()>> {
1366        let window_id = cx.window_id();
1367        let is_remote = self.project.read(cx).is_remote();
1368        let has_worktree = self.project.read(cx).worktrees(cx).next().is_some();
1369        let has_dirty_items = self.items(cx).any(|item| item.is_dirty(cx));
1370        let close_task = if is_remote || has_worktree || has_dirty_items {
1371            None
1372        } else {
1373            Some(self.prepare_to_close(false, cx))
1374        };
1375        let app_state = self.app_state.clone();
1376
1377        cx.spawn(|_, mut cx| async move {
1378            let window_id_to_replace = if let Some(close_task) = close_task {
1379                if !close_task.await? {
1380                    return Ok(());
1381                }
1382                Some(window_id)
1383            } else {
1384                None
1385            };
1386            cx.update(|cx| open_paths(&paths, &app_state, window_id_to_replace, cx))
1387                .await?;
1388            Ok(())
1389        })
1390    }
1391
1392    #[allow(clippy::type_complexity)]
1393    pub fn open_paths(
1394        &mut self,
1395        mut abs_paths: Vec<PathBuf>,
1396        visible: bool,
1397        cx: &mut ViewContext<Self>,
1398    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
1399        log::info!("open paths {:?}", abs_paths);
1400
1401        let fs = self.app_state.fs.clone();
1402
1403        // Sort the paths to ensure we add worktrees for parents before their children.
1404        abs_paths.sort_unstable();
1405        cx.spawn(|this, mut cx| async move {
1406            let mut project_paths = Vec::new();
1407            for path in &abs_paths {
1408                if let Some(project_path) = this
1409                    .update(&mut cx, |this, cx| {
1410                        Workspace::project_path_for_path(this.project.clone(), path, visible, cx)
1411                    })
1412                    .log_err()
1413                {
1414                    project_paths.push(project_path.await.log_err());
1415                } else {
1416                    project_paths.push(None);
1417                }
1418            }
1419
1420            let tasks = abs_paths
1421                .iter()
1422                .cloned()
1423                .zip(project_paths.into_iter())
1424                .map(|(abs_path, project_path)| {
1425                    let this = this.clone();
1426                    cx.spawn(|mut cx| {
1427                        let fs = fs.clone();
1428                        async move {
1429                            let (_worktree, project_path) = project_path?;
1430                            if fs.is_file(&abs_path).await {
1431                                Some(
1432                                    this.update(&mut cx, |this, cx| {
1433                                        this.open_path(project_path, None, true, cx)
1434                                    })
1435                                    .log_err()?
1436                                    .await,
1437                                )
1438                            } else {
1439                                None
1440                            }
1441                        }
1442                    })
1443                })
1444                .collect::<Vec<_>>();
1445
1446            futures::future::join_all(tasks).await
1447        })
1448    }
1449
1450    fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
1451        let mut paths = cx.prompt_for_paths(PathPromptOptions {
1452            files: false,
1453            directories: true,
1454            multiple: true,
1455        });
1456        cx.spawn(|this, mut cx| async move {
1457            if let Some(paths) = paths.recv().await.flatten() {
1458                let results = this
1459                    .update(&mut cx, |this, cx| this.open_paths(paths, true, cx))?
1460                    .await;
1461                for result in results.into_iter().flatten() {
1462                    result.log_err();
1463                }
1464            }
1465            anyhow::Ok(())
1466        })
1467        .detach_and_log_err(cx);
1468    }
1469
1470    fn project_path_for_path(
1471        project: ModelHandle<Project>,
1472        abs_path: &Path,
1473        visible: bool,
1474        cx: &mut AppContext,
1475    ) -> Task<Result<(ModelHandle<Worktree>, ProjectPath)>> {
1476        let entry = project.update(cx, |project, cx| {
1477            project.find_or_create_local_worktree(abs_path, visible, cx)
1478        });
1479        cx.spawn(|cx| async move {
1480            let (worktree, path) = entry.await?;
1481            let worktree_id = worktree.read_with(&cx, |t, _| t.id());
1482            Ok((
1483                worktree,
1484                ProjectPath {
1485                    worktree_id,
1486                    path: path.into(),
1487                },
1488            ))
1489        })
1490    }
1491
1492    /// Returns the modal that was toggled closed if it was open.
1493    pub fn toggle_modal<V, F>(
1494        &mut self,
1495        cx: &mut ViewContext<Self>,
1496        add_view: F,
1497    ) -> Option<ViewHandle<V>>
1498    where
1499        V: 'static + Modal,
1500        F: FnOnce(&mut Self, &mut ViewContext<Self>) -> ViewHandle<V>,
1501    {
1502        cx.notify();
1503        // Whatever modal was visible is getting clobbered. If its the same type as V, then return
1504        // it. Otherwise, create a new modal and set it as active.
1505        if let Some(already_open_modal) = self
1506            .dismiss_modal(cx)
1507            .and_then(|modal| modal.downcast::<V>())
1508        {
1509            cx.focus_self();
1510            Some(already_open_modal)
1511        } else {
1512            let modal = add_view(self, cx);
1513            cx.subscribe(&modal, |this, _, event, cx| {
1514                if V::dismiss_on_event(event) {
1515                    this.dismiss_modal(cx);
1516                }
1517            })
1518            .detach();
1519            let previously_focused_view_id = cx.focused_view_id();
1520            cx.focus(&modal);
1521            self.modal = Some(ActiveModal {
1522                view: Box::new(modal),
1523                previously_focused_view_id,
1524            });
1525            None
1526        }
1527    }
1528
1529    pub fn modal<V: 'static + View>(&self) -> Option<ViewHandle<V>> {
1530        self.modal
1531            .as_ref()
1532            .and_then(|modal| modal.view.as_any().clone().downcast::<V>())
1533    }
1534
1535    pub fn dismiss_modal(&mut self, cx: &mut ViewContext<Self>) -> Option<AnyViewHandle> {
1536        if let Some(modal) = self.modal.take() {
1537            if let Some(previously_focused_view_id) = modal.previously_focused_view_id {
1538                if modal.view.has_focus(cx) {
1539                    cx.window_context().focus(Some(previously_focused_view_id));
1540                }
1541            }
1542            cx.notify();
1543            Some(modal.view.as_any().clone())
1544        } else {
1545            None
1546        }
1547    }
1548
1549    pub fn items<'a>(
1550        &'a self,
1551        cx: &'a AppContext,
1552    ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
1553        self.panes.iter().flat_map(|pane| pane.read(cx).items())
1554    }
1555
1556    pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
1557        self.items_of_type(cx).max_by_key(|item| item.id())
1558    }
1559
1560    pub fn items_of_type<'a, T: Item>(
1561        &'a self,
1562        cx: &'a AppContext,
1563    ) -> impl 'a + Iterator<Item = ViewHandle<T>> {
1564        self.panes
1565            .iter()
1566            .flat_map(|pane| pane.read(cx).items_of_type())
1567    }
1568
1569    pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
1570        self.active_pane().read(cx).active_item()
1571    }
1572
1573    fn active_project_path(&self, cx: &ViewContext<Self>) -> Option<ProjectPath> {
1574        self.active_item(cx).and_then(|item| item.project_path(cx))
1575    }
1576
1577    pub fn save_active_item(
1578        &mut self,
1579        force_name_change: bool,
1580        cx: &mut ViewContext<Self>,
1581    ) -> Task<Result<()>> {
1582        let project = self.project.clone();
1583        if let Some(item) = self.active_item(cx) {
1584            if !force_name_change && item.can_save(cx) {
1585                if item.has_conflict(cx) {
1586                    const CONFLICT_MESSAGE: &str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
1587
1588                    let mut answer = cx.prompt(
1589                        PromptLevel::Warning,
1590                        CONFLICT_MESSAGE,
1591                        &["Overwrite", "Cancel"],
1592                    );
1593                    cx.spawn(|this, mut cx| async move {
1594                        let answer = answer.recv().await;
1595                        if answer == Some(0) {
1596                            this.update(&mut cx, |this, cx| item.save(this.project.clone(), cx))?
1597                                .await?;
1598                        }
1599                        Ok(())
1600                    })
1601                } else {
1602                    item.save(self.project.clone(), cx)
1603                }
1604            } else if item.is_singleton(cx) {
1605                let worktree = self.worktrees(cx).next();
1606                let start_abs_path = worktree
1607                    .and_then(|w| w.read(cx).as_local())
1608                    .map_or(Path::new(""), |w| w.abs_path())
1609                    .to_path_buf();
1610                let mut abs_path = cx.prompt_for_new_path(&start_abs_path);
1611                cx.spawn(|this, mut cx| async move {
1612                    if let Some(abs_path) = abs_path.recv().await.flatten() {
1613                        this.update(&mut cx, |_, cx| item.save_as(project, abs_path, cx))?
1614                            .await?;
1615                    }
1616                    Ok(())
1617                })
1618            } else {
1619                Task::ready(Ok(()))
1620            }
1621        } else {
1622            Task::ready(Ok(()))
1623        }
1624    }
1625
1626    pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext<Self>) {
1627        let dock = match dock_side {
1628            DockPosition::Left => &self.left_dock,
1629            DockPosition::Bottom => &self.bottom_dock,
1630            DockPosition::Right => &self.right_dock,
1631        };
1632        let mut focus_center = false;
1633        let mut reveal_dock = false;
1634        dock.update(cx, |dock, cx| {
1635            let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side);
1636            let was_visible = dock.is_open() && !other_is_zoomed;
1637            dock.set_open(!was_visible, cx);
1638
1639            if let Some(active_panel) = dock.active_panel() {
1640                if was_visible {
1641                    if active_panel.has_focus(cx) {
1642                        focus_center = true;
1643                    }
1644                } else {
1645                    cx.focus(active_panel.as_any());
1646                    reveal_dock = true;
1647                }
1648            }
1649        });
1650
1651        if reveal_dock {
1652            self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx);
1653        }
1654
1655        if focus_center {
1656            cx.focus_self();
1657        }
1658
1659        cx.notify();
1660        self.serialize_workspace(cx);
1661    }
1662
1663    /// Transfer focus to the panel of the given type.
1664    pub fn focus_panel<T: Panel>(&mut self, cx: &mut ViewContext<Self>) -> Option<ViewHandle<T>> {
1665        self.focus_or_unfocus_panel::<T>(cx, |_, _| true)?
1666            .as_any()
1667            .clone()
1668            .downcast()
1669    }
1670
1671    /// Focus the panel of the given type if it isn't already focused. If it is
1672    /// already focused, then transfer focus back to the workspace center.
1673    pub fn toggle_panel_focus<T: Panel>(&mut self, cx: &mut ViewContext<Self>) {
1674        self.focus_or_unfocus_panel::<T>(cx, |panel, cx| !panel.has_focus(cx));
1675    }
1676
1677    /// Focus or unfocus the given panel type, depending on the given callback.
1678    fn focus_or_unfocus_panel<T: Panel>(
1679        &mut self,
1680        cx: &mut ViewContext<Self>,
1681        should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext<Dock>) -> bool,
1682    ) -> Option<Rc<dyn PanelHandle>> {
1683        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1684            if let Some(panel_index) = dock.read(cx).panel_index_for_type::<T>() {
1685                let mut focus_center = false;
1686                let mut reveal_dock = false;
1687                let panel = dock.update(cx, |dock, cx| {
1688                    dock.activate_panel(panel_index, cx);
1689
1690                    let panel = dock.active_panel().cloned();
1691                    if let Some(panel) = panel.as_ref() {
1692                        if should_focus(&**panel, cx) {
1693                            dock.set_open(true, cx);
1694                            cx.focus(panel.as_any());
1695                            reveal_dock = true;
1696                        } else {
1697                            // if panel.is_zoomed(cx) {
1698                            //     dock.set_open(false, cx);
1699                            // }
1700                            focus_center = true;
1701                        }
1702                    }
1703                    panel
1704                });
1705
1706                if focus_center {
1707                    cx.focus_self();
1708                }
1709
1710                self.serialize_workspace(cx);
1711                cx.notify();
1712                return panel;
1713            }
1714        }
1715        None
1716    }
1717
1718    pub fn panel<T: Panel>(&self, cx: &WindowContext) -> Option<ViewHandle<T>> {
1719        for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] {
1720            let dock = dock.read(cx);
1721            if let Some(panel) = dock.panel::<T>() {
1722                return Some(panel);
1723            }
1724        }
1725        None
1726    }
1727
1728    fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
1729        for pane in &self.panes {
1730            pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1731        }
1732
1733        self.left_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1734        self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1735        self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx));
1736        self.zoomed = None;
1737        self.zoomed_position = None;
1738
1739        cx.notify();
1740    }
1741
1742    #[cfg(any(test, feature = "test-support"))]
1743    pub fn zoomed_view(&self, cx: &AppContext) -> Option<AnyViewHandle> {
1744        self.zoomed.and_then(|view| view.upgrade(cx))
1745    }
1746
1747    fn dismiss_zoomed_items_to_reveal(
1748        &mut self,
1749        dock_to_reveal: Option<DockPosition>,
1750        cx: &mut ViewContext<Self>,
1751    ) {
1752        // If a center pane is zoomed, unzoom it.
1753        for pane in &self.panes {
1754            if pane != &self.active_pane || dock_to_reveal.is_some() {
1755                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
1756            }
1757        }
1758
1759        // If another dock is zoomed, hide it.
1760        let mut focus_center = false;
1761        for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] {
1762            dock.update(cx, |dock, cx| {
1763                if Some(dock.position()) != dock_to_reveal {
1764                    if let Some(panel) = dock.active_panel() {
1765                        if panel.is_zoomed(cx) {
1766                            focus_center |= panel.has_focus(cx);
1767                            dock.set_open(false, cx);
1768                        }
1769                    }
1770                }
1771            });
1772        }
1773
1774        if focus_center {
1775            cx.focus_self();
1776        }
1777
1778        if self.zoomed_position != dock_to_reveal {
1779            self.zoomed = None;
1780            self.zoomed_position = None;
1781        }
1782
1783        cx.notify();
1784    }
1785
1786    fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
1787        let pane = cx.add_view(|cx| {
1788            Pane::new(
1789                self.weak_handle(),
1790                self.project.clone(),
1791                self.app_state.background_actions,
1792                self.pane_history_timestamp.clone(),
1793                cx,
1794            )
1795        });
1796        cx.subscribe(&pane, Self::handle_pane_event).detach();
1797        self.panes.push(pane.clone());
1798        cx.focus(&pane);
1799        cx.emit(Event::PaneAdded(pane.clone()));
1800        pane
1801    }
1802
1803    pub fn add_item_to_center(
1804        &mut self,
1805        item: Box<dyn ItemHandle>,
1806        cx: &mut ViewContext<Self>,
1807    ) -> bool {
1808        if let Some(center_pane) = self.last_active_center_pane.clone() {
1809            if let Some(center_pane) = center_pane.upgrade(cx) {
1810                center_pane.update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1811                true
1812            } else {
1813                false
1814            }
1815        } else {
1816            false
1817        }
1818    }
1819
1820    pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1821        self.active_pane
1822            .update(cx, |pane, cx| pane.add_item(item, true, true, None, cx));
1823    }
1824
1825    pub fn split_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
1826        let new_pane = self.split_pane(self.active_pane.clone(), SplitDirection::Right, cx);
1827        new_pane.update(cx, move |new_pane, cx| {
1828            new_pane.add_item(item, true, true, None, cx)
1829        })
1830    }
1831
1832    pub fn open_abs_path(
1833        &mut self,
1834        abs_path: PathBuf,
1835        visible: bool,
1836        cx: &mut ViewContext<Self>,
1837    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1838        cx.spawn(|workspace, mut cx| async move {
1839            let open_paths_task_result = workspace
1840                .update(&mut cx, |workspace, cx| {
1841                    workspace.open_paths(vec![abs_path.clone()], visible, cx)
1842                })
1843                .with_context(|| format!("open abs path {abs_path:?} task spawn"))?
1844                .await;
1845            anyhow::ensure!(
1846                open_paths_task_result.len() == 1,
1847                "open abs path {abs_path:?} task returned incorrect number of results"
1848            );
1849            match open_paths_task_result
1850                .into_iter()
1851                .next()
1852                .expect("ensured single task result")
1853            {
1854                Some(open_result) => {
1855                    open_result.with_context(|| format!("open abs path {abs_path:?} task join"))
1856                }
1857                None => anyhow::bail!("open abs path {abs_path:?} task returned None"),
1858            }
1859        })
1860    }
1861
1862    pub fn split_abs_path(
1863        &mut self,
1864        abs_path: PathBuf,
1865        visible: bool,
1866        cx: &mut ViewContext<Self>,
1867    ) -> Task<anyhow::Result<Box<dyn ItemHandle>>> {
1868        let project_path_task =
1869            Workspace::project_path_for_path(self.project.clone(), &abs_path, visible, cx);
1870        cx.spawn(|this, mut cx| async move {
1871            let (_, path) = project_path_task.await?;
1872            this.update(&mut cx, |this, cx| this.split_path(path, cx))?
1873                .await
1874        })
1875    }
1876
1877    pub fn open_path(
1878        &mut self,
1879        path: impl Into<ProjectPath>,
1880        pane: Option<WeakViewHandle<Pane>>,
1881        focus_item: bool,
1882        cx: &mut ViewContext<Self>,
1883    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1884        let pane = pane.unwrap_or_else(|| {
1885            self.last_active_center_pane.clone().unwrap_or_else(|| {
1886                self.panes
1887                    .first()
1888                    .expect("There must be an active pane")
1889                    .downgrade()
1890            })
1891        });
1892
1893        let task = self.load_path(path.into(), cx);
1894        cx.spawn(|_, mut cx| async move {
1895            let (project_entry_id, build_item) = task.await?;
1896            pane.update(&mut cx, |pane, cx| {
1897                pane.open_item(project_entry_id, focus_item, cx, build_item)
1898            })
1899        })
1900    }
1901
1902    pub fn split_path(
1903        &mut self,
1904        path: impl Into<ProjectPath>,
1905        cx: &mut ViewContext<Self>,
1906    ) -> Task<Result<Box<dyn ItemHandle>, anyhow::Error>> {
1907        let pane = self.last_active_center_pane.clone().unwrap_or_else(|| {
1908            self.panes
1909                .first()
1910                .expect("There must be an active pane")
1911                .downgrade()
1912        });
1913
1914        if let Member::Pane(center_pane) = &self.center.root {
1915            if center_pane.read(cx).items_len() == 0 {
1916                return self.open_path(path, Some(pane), true, cx);
1917            }
1918        }
1919
1920        let task = self.load_path(path.into(), cx);
1921        cx.spawn(|this, mut cx| async move {
1922            let (project_entry_id, build_item) = task.await?;
1923            this.update(&mut cx, move |this, cx| -> Option<_> {
1924                let pane = pane.upgrade(cx)?;
1925                let new_pane = this.split_pane(pane, SplitDirection::Right, cx);
1926                new_pane.update(cx, |new_pane, cx| {
1927                    Some(new_pane.open_item(project_entry_id, true, cx, build_item))
1928                })
1929            })
1930            .map(|option| option.ok_or_else(|| anyhow!("pane was dropped")))?
1931        })
1932    }
1933
1934    pub(crate) fn load_path(
1935        &mut self,
1936        path: ProjectPath,
1937        cx: &mut ViewContext<Self>,
1938    ) -> Task<
1939        Result<(
1940            ProjectEntryId,
1941            impl 'static + FnOnce(&mut ViewContext<Pane>) -> Box<dyn ItemHandle>,
1942        )>,
1943    > {
1944        let project = self.project().clone();
1945        let project_item = project.update(cx, |project, cx| project.open_path(path, cx));
1946        cx.spawn(|_, mut cx| async move {
1947            let (project_entry_id, project_item) = project_item.await?;
1948            let build_item = cx.update(|cx| {
1949                cx.default_global::<ProjectItemBuilders>()
1950                    .get(&project_item.model_type())
1951                    .ok_or_else(|| anyhow!("no item builder for project item"))
1952                    .cloned()
1953            })?;
1954            let build_item =
1955                move |cx: &mut ViewContext<Pane>| build_item(project, project_item, cx);
1956            Ok((project_entry_id, build_item))
1957        })
1958    }
1959
1960    pub fn open_project_item<T>(
1961        &mut self,
1962        project_item: ModelHandle<T::Item>,
1963        cx: &mut ViewContext<Self>,
1964    ) -> ViewHandle<T>
1965    where
1966        T: ProjectItem,
1967    {
1968        use project::Item as _;
1969
1970        let entry_id = project_item.read(cx).entry_id(cx);
1971        if let Some(item) = entry_id
1972            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1973            .and_then(|item| item.downcast())
1974        {
1975            self.activate_item(&item, cx);
1976            return item;
1977        }
1978
1979        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
1980        self.add_item(Box::new(item.clone()), cx);
1981        item
1982    }
1983
1984    pub fn split_project_item<T>(
1985        &mut self,
1986        project_item: ModelHandle<T::Item>,
1987        cx: &mut ViewContext<Self>,
1988    ) -> ViewHandle<T>
1989    where
1990        T: ProjectItem,
1991    {
1992        use project::Item as _;
1993
1994        let entry_id = project_item.read(cx).entry_id(cx);
1995        if let Some(item) = entry_id
1996            .and_then(|entry_id| self.active_pane().read(cx).item_for_entry(entry_id, cx))
1997            .and_then(|item| item.downcast())
1998        {
1999            self.activate_item(&item, cx);
2000            return item;
2001        }
2002
2003        let item = cx.add_view(|cx| T::for_project_item(self.project().clone(), project_item, cx));
2004        self.split_item(Box::new(item.clone()), cx);
2005        item
2006    }
2007
2008    pub fn open_shared_screen(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2009        if let Some(shared_screen) = self.shared_screen_for_peer(peer_id, &self.active_pane, cx) {
2010            self.active_pane.update(cx, |pane, cx| {
2011                pane.add_item(Box::new(shared_screen), false, true, None, cx)
2012            });
2013        }
2014    }
2015
2016    pub fn activate_item(&mut self, item: &dyn ItemHandle, cx: &mut ViewContext<Self>) -> bool {
2017        let result = self.panes.iter().find_map(|pane| {
2018            pane.read(cx)
2019                .index_for_item(item)
2020                .map(|ix| (pane.clone(), ix))
2021        });
2022        if let Some((pane, ix)) = result {
2023            pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
2024            true
2025        } else {
2026            false
2027        }
2028    }
2029
2030    fn activate_pane_at_index(&mut self, action: &ActivatePane, cx: &mut ViewContext<Self>) {
2031        let panes = self.center.panes();
2032        if let Some(pane) = panes.get(action.0).map(|p| (*p).clone()) {
2033            cx.focus(&pane);
2034        } else {
2035            self.split_and_clone(self.active_pane.clone(), SplitDirection::Right, cx);
2036        }
2037    }
2038
2039    pub fn activate_next_pane(&mut self, cx: &mut ViewContext<Self>) {
2040        let panes = self.center.panes();
2041        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2042            let next_ix = (ix + 1) % panes.len();
2043            let next_pane = panes[next_ix].clone();
2044            cx.focus(&next_pane);
2045        }
2046    }
2047
2048    pub fn activate_previous_pane(&mut self, cx: &mut ViewContext<Self>) {
2049        let panes = self.center.panes();
2050        if let Some(ix) = panes.iter().position(|pane| **pane == self.active_pane) {
2051            let prev_ix = cmp::min(ix.wrapping_sub(1), panes.len() - 1);
2052            let prev_pane = panes[prev_ix].clone();
2053            cx.focus(&prev_pane);
2054        }
2055    }
2056
2057    fn handle_pane_focused(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2058        if self.active_pane != pane {
2059            self.active_pane = pane.clone();
2060            self.status_bar.update(cx, |status_bar, cx| {
2061                status_bar.set_active_pane(&self.active_pane, cx);
2062            });
2063            self.active_item_path_changed(cx);
2064            self.last_active_center_pane = Some(pane.downgrade());
2065        }
2066
2067        self.dismiss_zoomed_items_to_reveal(None, cx);
2068        if pane.read(cx).is_zoomed() {
2069            self.zoomed = Some(pane.downgrade().into_any());
2070        } else {
2071            self.zoomed = None;
2072        }
2073        self.zoomed_position = None;
2074        self.update_active_view_for_followers(cx);
2075
2076        cx.notify();
2077    }
2078
2079    fn handle_pane_event(
2080        &mut self,
2081        pane: ViewHandle<Pane>,
2082        event: &pane::Event,
2083        cx: &mut ViewContext<Self>,
2084    ) {
2085        match event {
2086            pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx),
2087            pane::Event::Split(direction) => {
2088                self.split_and_clone(pane, *direction, cx);
2089            }
2090            pane::Event::Remove => self.remove_pane(pane, cx),
2091            pane::Event::ActivateItem { local } => {
2092                if *local {
2093                    self.unfollow(&pane, cx);
2094                }
2095                if &pane == self.active_pane() {
2096                    self.active_item_path_changed(cx);
2097                }
2098            }
2099            pane::Event::ChangeItemTitle => {
2100                if pane == self.active_pane {
2101                    self.active_item_path_changed(cx);
2102                }
2103                self.update_window_edited(cx);
2104            }
2105            pane::Event::RemoveItem { item_id } => {
2106                self.update_window_edited(cx);
2107                if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
2108                    if entry.get().id() == pane.id() {
2109                        entry.remove();
2110                    }
2111                }
2112            }
2113            pane::Event::Focus => {
2114                self.handle_pane_focused(pane.clone(), cx);
2115            }
2116            pane::Event::ZoomIn => {
2117                if pane == self.active_pane {
2118                    pane.update(cx, |pane, cx| pane.set_zoomed(true, cx));
2119                    if pane.read(cx).has_focus() {
2120                        self.zoomed = Some(pane.downgrade().into_any());
2121                        self.zoomed_position = None;
2122                    }
2123                    cx.notify();
2124                }
2125            }
2126            pane::Event::ZoomOut => {
2127                pane.update(cx, |pane, cx| pane.set_zoomed(false, cx));
2128                if self.zoomed_position.is_none() {
2129                    self.zoomed = None;
2130                }
2131                cx.notify();
2132            }
2133        }
2134
2135        self.serialize_workspace(cx);
2136    }
2137
2138    pub fn split_pane(
2139        &mut self,
2140        pane_to_split: ViewHandle<Pane>,
2141        split_direction: SplitDirection,
2142        cx: &mut ViewContext<Self>,
2143    ) -> ViewHandle<Pane> {
2144        let new_pane = self.add_pane(cx);
2145        self.center
2146            .split(&pane_to_split, &new_pane, split_direction)
2147            .unwrap();
2148        cx.notify();
2149        new_pane
2150    }
2151
2152    pub fn split_and_clone(
2153        &mut self,
2154        pane: ViewHandle<Pane>,
2155        direction: SplitDirection,
2156        cx: &mut ViewContext<Self>,
2157    ) -> Option<ViewHandle<Pane>> {
2158        let item = pane.read(cx).active_item()?;
2159        let maybe_pane_handle = if let Some(clone) = item.clone_on_split(self.database_id(), cx) {
2160            let new_pane = self.add_pane(cx);
2161            new_pane.update(cx, |pane, cx| pane.add_item(clone, true, true, None, cx));
2162            self.center.split(&pane, &new_pane, direction).unwrap();
2163            Some(new_pane)
2164        } else {
2165            None
2166        };
2167        cx.notify();
2168        maybe_pane_handle
2169    }
2170
2171    pub fn split_pane_with_item(
2172        &mut self,
2173        pane_to_split: WeakViewHandle<Pane>,
2174        split_direction: SplitDirection,
2175        from: WeakViewHandle<Pane>,
2176        item_id_to_move: usize,
2177        cx: &mut ViewContext<Self>,
2178    ) {
2179        let Some(pane_to_split) = pane_to_split.upgrade(cx) else { return; };
2180        let Some(from) = from.upgrade(cx) else { return; };
2181
2182        let new_pane = self.add_pane(cx);
2183        self.move_item(from.clone(), new_pane.clone(), item_id_to_move, 0, cx);
2184        self.center
2185            .split(&pane_to_split, &new_pane, split_direction)
2186            .unwrap();
2187        cx.notify();
2188    }
2189
2190    pub fn split_pane_with_project_entry(
2191        &mut self,
2192        pane_to_split: WeakViewHandle<Pane>,
2193        split_direction: SplitDirection,
2194        project_entry: ProjectEntryId,
2195        cx: &mut ViewContext<Self>,
2196    ) -> Option<Task<Result<()>>> {
2197        let pane_to_split = pane_to_split.upgrade(cx)?;
2198        let new_pane = self.add_pane(cx);
2199        self.center
2200            .split(&pane_to_split, &new_pane, split_direction)
2201            .unwrap();
2202
2203        let path = self.project.read(cx).path_for_entry(project_entry, cx)?;
2204        let task = self.open_path(path, Some(new_pane.downgrade()), true, cx);
2205        Some(cx.foreground().spawn(async move {
2206            task.await?;
2207            Ok(())
2208        }))
2209    }
2210
2211    pub fn move_item(
2212        &mut self,
2213        source: ViewHandle<Pane>,
2214        destination: ViewHandle<Pane>,
2215        item_id_to_move: usize,
2216        destination_index: usize,
2217        cx: &mut ViewContext<Self>,
2218    ) {
2219        let item_to_move = source
2220            .read(cx)
2221            .items()
2222            .enumerate()
2223            .find(|(_, item_handle)| item_handle.id() == item_id_to_move);
2224
2225        if item_to_move.is_none() {
2226            log::warn!("Tried to move item handle which was not in `from` pane. Maybe tab was closed during drop");
2227            return;
2228        }
2229        let (item_ix, item_handle) = item_to_move.unwrap();
2230        let item_handle = item_handle.clone();
2231
2232        if source != destination {
2233            // Close item from previous pane
2234            source.update(cx, |source, cx| {
2235                source.remove_item(item_ix, false, cx);
2236            });
2237        }
2238
2239        // This automatically removes duplicate items in the pane
2240        destination.update(cx, |destination, cx| {
2241            destination.add_item(item_handle, true, true, Some(destination_index), cx);
2242            cx.focus_self();
2243        });
2244    }
2245
2246    fn remove_pane(&mut self, pane: ViewHandle<Pane>, cx: &mut ViewContext<Self>) {
2247        if self.center.remove(&pane).unwrap() {
2248            self.force_remove_pane(&pane, cx);
2249            self.unfollow(&pane, cx);
2250            self.last_leaders_by_pane.remove(&pane.downgrade());
2251            for removed_item in pane.read(cx).items() {
2252                self.panes_by_item.remove(&removed_item.id());
2253            }
2254
2255            cx.notify();
2256        } else {
2257            self.active_item_path_changed(cx);
2258        }
2259    }
2260
2261    pub fn panes(&self) -> &[ViewHandle<Pane>] {
2262        &self.panes
2263    }
2264
2265    pub fn active_pane(&self) -> &ViewHandle<Pane> {
2266        &self.active_pane
2267    }
2268
2269    fn project_remote_id_changed(&mut self, remote_id: Option<u64>, cx: &mut ViewContext<Self>) {
2270        if let Some(remote_id) = remote_id {
2271            self.remote_entity_subscription = Some(
2272                self.app_state
2273                    .client
2274                    .add_view_for_remote_entity(remote_id, cx),
2275            );
2276        } else {
2277            self.remote_entity_subscription.take();
2278        }
2279    }
2280
2281    fn collaborator_left(&mut self, peer_id: PeerId, cx: &mut ViewContext<Self>) {
2282        self.leader_state.followers.remove(&peer_id);
2283        if let Some(states_by_pane) = self.follower_states_by_leader.remove(&peer_id) {
2284            for state in states_by_pane.into_values() {
2285                for item in state.items_by_leader_view_id.into_values() {
2286                    item.set_leader_replica_id(None, cx);
2287                }
2288            }
2289        }
2290        cx.notify();
2291    }
2292
2293    pub fn toggle_follow(
2294        &mut self,
2295        leader_id: PeerId,
2296        cx: &mut ViewContext<Self>,
2297    ) -> Option<Task<Result<()>>> {
2298        let pane = self.active_pane().clone();
2299
2300        if let Some(prev_leader_id) = self.unfollow(&pane, cx) {
2301            if leader_id == prev_leader_id {
2302                return None;
2303            }
2304        }
2305
2306        self.last_leaders_by_pane
2307            .insert(pane.downgrade(), leader_id);
2308        self.follower_states_by_leader
2309            .entry(leader_id)
2310            .or_default()
2311            .insert(pane.clone(), Default::default());
2312        cx.notify();
2313
2314        let project_id = self.project.read(cx).remote_id()?;
2315        let request = self.app_state.client.request(proto::Follow {
2316            project_id,
2317            leader_id: Some(leader_id),
2318        });
2319
2320        Some(cx.spawn(|this, mut cx| async move {
2321            let response = request.await?;
2322            this.update(&mut cx, |this, _| {
2323                let state = this
2324                    .follower_states_by_leader
2325                    .get_mut(&leader_id)
2326                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
2327                    .ok_or_else(|| anyhow!("following interrupted"))?;
2328                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
2329                    Some(ViewId::from_proto(active_view_id)?)
2330                } else {
2331                    None
2332                };
2333                Ok::<_, anyhow::Error>(())
2334            })??;
2335            Self::add_views_from_leader(
2336                this.clone(),
2337                leader_id,
2338                vec![pane],
2339                response.views,
2340                &mut cx,
2341            )
2342            .await?;
2343            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
2344            Ok(())
2345        }))
2346    }
2347
2348    pub fn follow_next_collaborator(
2349        &mut self,
2350        _: &FollowNextCollaborator,
2351        cx: &mut ViewContext<Self>,
2352    ) -> Option<Task<Result<()>>> {
2353        let collaborators = self.project.read(cx).collaborators();
2354        let next_leader_id = if let Some(leader_id) = self.leader_for_pane(&self.active_pane) {
2355            let mut collaborators = collaborators.keys().copied();
2356            for peer_id in collaborators.by_ref() {
2357                if peer_id == leader_id {
2358                    break;
2359                }
2360            }
2361            collaborators.next()
2362        } else if let Some(last_leader_id) =
2363            self.last_leaders_by_pane.get(&self.active_pane.downgrade())
2364        {
2365            if collaborators.contains_key(last_leader_id) {
2366                Some(*last_leader_id)
2367            } else {
2368                None
2369            }
2370        } else {
2371            None
2372        };
2373
2374        next_leader_id
2375            .or_else(|| collaborators.keys().copied().next())
2376            .and_then(|leader_id| self.toggle_follow(leader_id, cx))
2377    }
2378
2379    pub fn unfollow(
2380        &mut self,
2381        pane: &ViewHandle<Pane>,
2382        cx: &mut ViewContext<Self>,
2383    ) -> Option<PeerId> {
2384        for (leader_id, states_by_pane) in &mut self.follower_states_by_leader {
2385            let leader_id = *leader_id;
2386            if let Some(state) = states_by_pane.remove(pane) {
2387                for (_, item) in state.items_by_leader_view_id {
2388                    item.set_leader_replica_id(None, cx);
2389                }
2390
2391                if states_by_pane.is_empty() {
2392                    self.follower_states_by_leader.remove(&leader_id);
2393                    if let Some(project_id) = self.project.read(cx).remote_id() {
2394                        self.app_state
2395                            .client
2396                            .send(proto::Unfollow {
2397                                project_id,
2398                                leader_id: Some(leader_id),
2399                            })
2400                            .log_err();
2401                    }
2402                }
2403
2404                cx.notify();
2405                return Some(leader_id);
2406            }
2407        }
2408        None
2409    }
2410
2411    pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
2412        self.follower_states_by_leader.contains_key(&peer_id)
2413    }
2414
2415    pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
2416        self.leader_state.followers.contains(&peer_id)
2417    }
2418
2419    fn render_titlebar(&self, theme: &Theme, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
2420        // TODO: There should be a better system in place for this
2421        // (https://github.com/zed-industries/zed/issues/1290)
2422        let is_fullscreen = cx.window_is_fullscreen();
2423        let container_theme = if is_fullscreen {
2424            let mut container_theme = theme.titlebar.container;
2425            container_theme.padding.left = container_theme.padding.right;
2426            container_theme
2427        } else {
2428            theme.titlebar.container
2429        };
2430
2431        enum TitleBar {}
2432        MouseEventHandler::<TitleBar, _>::new(0, cx, |_, cx| {
2433            Stack::new()
2434                .with_children(
2435                    self.titlebar_item
2436                        .as_ref()
2437                        .map(|item| ChildView::new(item, cx)),
2438                )
2439                .contained()
2440                .with_style(container_theme)
2441        })
2442        .on_click(MouseButton::Left, |event, _, cx| {
2443            if event.click_count == 2 {
2444                cx.zoom_window();
2445            }
2446        })
2447        .constrained()
2448        .with_height(theme.titlebar.height)
2449        .into_any_named("titlebar")
2450    }
2451
2452    fn active_item_path_changed(&mut self, cx: &mut ViewContext<Self>) {
2453        let active_entry = self.active_project_path(cx);
2454        self.project
2455            .update(cx, |project, cx| project.set_active_path(active_entry, cx));
2456        self.update_window_title(cx);
2457    }
2458
2459    fn update_window_title(&mut self, cx: &mut ViewContext<Self>) {
2460        let project = self.project().read(cx);
2461        let mut title = String::new();
2462
2463        if let Some(path) = self.active_item(cx).and_then(|item| item.project_path(cx)) {
2464            let filename = path
2465                .path
2466                .file_name()
2467                .map(|s| s.to_string_lossy())
2468                .or_else(|| {
2469                    Some(Cow::Borrowed(
2470                        project
2471                            .worktree_for_id(path.worktree_id, cx)?
2472                            .read(cx)
2473                            .root_name(),
2474                    ))
2475                });
2476
2477            if let Some(filename) = filename {
2478                title.push_str(filename.as_ref());
2479                title.push_str(" β€” ");
2480            }
2481        }
2482
2483        for (i, name) in project.worktree_root_names(cx).enumerate() {
2484            if i > 0 {
2485                title.push_str(", ");
2486            }
2487            title.push_str(name);
2488        }
2489
2490        if title.is_empty() {
2491            title = "empty project".to_string();
2492        }
2493
2494        if project.is_remote() {
2495            title.push_str(" ↙");
2496        } else if project.is_shared() {
2497            title.push_str(" β†—");
2498        }
2499
2500        cx.set_window_title(&title);
2501    }
2502
2503    fn update_window_edited(&mut self, cx: &mut ViewContext<Self>) {
2504        let is_edited = !self.project.read(cx).is_read_only()
2505            && self
2506                .items(cx)
2507                .any(|item| item.has_conflict(cx) || item.is_dirty(cx));
2508        if is_edited != self.window_edited {
2509            self.window_edited = is_edited;
2510            cx.set_window_edited(self.window_edited)
2511        }
2512    }
2513
2514    fn render_disconnected_overlay(
2515        &self,
2516        cx: &mut ViewContext<Workspace>,
2517    ) -> Option<AnyElement<Workspace>> {
2518        if self.project.read(cx).is_read_only() {
2519            enum DisconnectedOverlay {}
2520            Some(
2521                MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
2522                    let theme = &theme::current(cx);
2523                    Label::new(
2524                        "Your connection to the remote project has been lost.",
2525                        theme.workspace.disconnected_overlay.text.clone(),
2526                    )
2527                    .aligned()
2528                    .contained()
2529                    .with_style(theme.workspace.disconnected_overlay.container)
2530                })
2531                .with_cursor_style(CursorStyle::Arrow)
2532                .capture_all()
2533                .into_any_named("disconnected overlay"),
2534            )
2535        } else {
2536            None
2537        }
2538    }
2539
2540    fn render_notifications(
2541        &self,
2542        theme: &theme::Workspace,
2543        cx: &AppContext,
2544    ) -> Option<AnyElement<Workspace>> {
2545        if self.notifications.is_empty() {
2546            None
2547        } else {
2548            Some(
2549                Flex::column()
2550                    .with_children(self.notifications.iter().map(|(_, _, notification)| {
2551                        ChildView::new(notification.as_any(), cx)
2552                            .contained()
2553                            .with_style(theme.notification)
2554                    }))
2555                    .constrained()
2556                    .with_width(theme.notifications.width)
2557                    .contained()
2558                    .with_style(theme.notifications.container)
2559                    .aligned()
2560                    .bottom()
2561                    .right()
2562                    .into_any(),
2563            )
2564        }
2565    }
2566
2567    // RPC handlers
2568
2569    async fn handle_follow(
2570        this: WeakViewHandle<Self>,
2571        envelope: TypedEnvelope<proto::Follow>,
2572        _: Arc<Client>,
2573        mut cx: AsyncAppContext,
2574    ) -> Result<proto::FollowResponse> {
2575        this.update(&mut cx, |this, cx| {
2576            let client = &this.app_state.client;
2577            this.leader_state
2578                .followers
2579                .insert(envelope.original_sender_id()?);
2580
2581            let active_view_id = this.active_item(cx).and_then(|i| {
2582                Some(
2583                    i.to_followable_item_handle(cx)?
2584                        .remote_id(client, cx)?
2585                        .to_proto(),
2586                )
2587            });
2588
2589            cx.notify();
2590
2591            Ok(proto::FollowResponse {
2592                active_view_id,
2593                views: this
2594                    .panes()
2595                    .iter()
2596                    .flat_map(|pane| {
2597                        let leader_id = this.leader_for_pane(pane);
2598                        pane.read(cx).items().filter_map({
2599                            let cx = &cx;
2600                            move |item| {
2601                                let item = item.to_followable_item_handle(cx)?;
2602                                let id = item.remote_id(client, cx)?.to_proto();
2603                                let variant = item.to_state_proto(cx)?;
2604                                Some(proto::View {
2605                                    id: Some(id),
2606                                    leader_id,
2607                                    variant: Some(variant),
2608                                })
2609                            }
2610                        })
2611                    })
2612                    .collect(),
2613            })
2614        })?
2615    }
2616
2617    async fn handle_unfollow(
2618        this: WeakViewHandle<Self>,
2619        envelope: TypedEnvelope<proto::Unfollow>,
2620        _: Arc<Client>,
2621        mut cx: AsyncAppContext,
2622    ) -> Result<()> {
2623        this.update(&mut cx, |this, cx| {
2624            this.leader_state
2625                .followers
2626                .remove(&envelope.original_sender_id()?);
2627            cx.notify();
2628            Ok(())
2629        })?
2630    }
2631
2632    async fn handle_update_followers(
2633        this: WeakViewHandle<Self>,
2634        envelope: TypedEnvelope<proto::UpdateFollowers>,
2635        _: Arc<Client>,
2636        cx: AsyncAppContext,
2637    ) -> Result<()> {
2638        let leader_id = envelope.original_sender_id()?;
2639        this.read_with(&cx, |this, _| {
2640            this.leader_updates_tx
2641                .unbounded_send((leader_id, envelope.payload))
2642        })??;
2643        Ok(())
2644    }
2645
2646    async fn process_leader_update(
2647        this: &WeakViewHandle<Self>,
2648        leader_id: PeerId,
2649        update: proto::UpdateFollowers,
2650        cx: &mut AsyncAppContext,
2651    ) -> Result<()> {
2652        match update.variant.ok_or_else(|| anyhow!("invalid update"))? {
2653            proto::update_followers::Variant::UpdateActiveView(update_active_view) => {
2654                this.update(cx, |this, _| {
2655                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2656                        for state in state.values_mut() {
2657                            state.active_view_id =
2658                                if let Some(active_view_id) = update_active_view.id.clone() {
2659                                    Some(ViewId::from_proto(active_view_id)?)
2660                                } else {
2661                                    None
2662                                };
2663                        }
2664                    }
2665                    anyhow::Ok(())
2666                })??;
2667            }
2668            proto::update_followers::Variant::UpdateView(update_view) => {
2669                let variant = update_view
2670                    .variant
2671                    .ok_or_else(|| anyhow!("missing update view variant"))?;
2672                let id = update_view
2673                    .id
2674                    .ok_or_else(|| anyhow!("missing update view id"))?;
2675                let mut tasks = Vec::new();
2676                this.update(cx, |this, cx| {
2677                    let project = this.project.clone();
2678                    if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) {
2679                        for state in state.values_mut() {
2680                            let view_id = ViewId::from_proto(id.clone())?;
2681                            if let Some(item) = state.items_by_leader_view_id.get(&view_id) {
2682                                tasks.push(item.apply_update_proto(&project, variant.clone(), cx));
2683                            }
2684                        }
2685                    }
2686                    anyhow::Ok(())
2687                })??;
2688                try_join_all(tasks).await.log_err();
2689            }
2690            proto::update_followers::Variant::CreateView(view) => {
2691                let panes = this.read_with(cx, |this, _| {
2692                    this.follower_states_by_leader
2693                        .get(&leader_id)
2694                        .into_iter()
2695                        .flat_map(|states_by_pane| states_by_pane.keys())
2696                        .cloned()
2697                        .collect()
2698                })?;
2699                Self::add_views_from_leader(this.clone(), leader_id, panes, vec![view], cx).await?;
2700            }
2701        }
2702        this.update(cx, |this, cx| this.leader_updated(leader_id, cx))?;
2703        Ok(())
2704    }
2705
2706    async fn add_views_from_leader(
2707        this: WeakViewHandle<Self>,
2708        leader_id: PeerId,
2709        panes: Vec<ViewHandle<Pane>>,
2710        views: Vec<proto::View>,
2711        cx: &mut AsyncAppContext,
2712    ) -> Result<()> {
2713        let project = this.read_with(cx, |this, _| this.project.clone())?;
2714        let replica_id = project
2715            .read_with(cx, |project, _| {
2716                project
2717                    .collaborators()
2718                    .get(&leader_id)
2719                    .map(|c| c.replica_id)
2720            })
2721            .ok_or_else(|| anyhow!("no such collaborator {}", leader_id))?;
2722
2723        let item_builders = cx.update(|cx| {
2724            cx.default_global::<FollowableItemBuilders>()
2725                .values()
2726                .map(|b| b.0)
2727                .collect::<Vec<_>>()
2728        });
2729
2730        let mut item_tasks_by_pane = HashMap::default();
2731        for pane in panes {
2732            let mut item_tasks = Vec::new();
2733            let mut leader_view_ids = Vec::new();
2734            for view in &views {
2735                let Some(id) = &view.id else { continue };
2736                let id = ViewId::from_proto(id.clone())?;
2737                let mut variant = view.variant.clone();
2738                if variant.is_none() {
2739                    Err(anyhow!("missing variant"))?;
2740                }
2741                for build_item in &item_builders {
2742                    let task = cx.update(|cx| {
2743                        build_item(pane.clone(), project.clone(), id, &mut variant, cx)
2744                    });
2745                    if let Some(task) = task {
2746                        item_tasks.push(task);
2747                        leader_view_ids.push(id);
2748                        break;
2749                    } else {
2750                        assert!(variant.is_some());
2751                    }
2752                }
2753            }
2754
2755            item_tasks_by_pane.insert(pane, (item_tasks, leader_view_ids));
2756        }
2757
2758        for (pane, (item_tasks, leader_view_ids)) in item_tasks_by_pane {
2759            let items = futures::future::try_join_all(item_tasks).await?;
2760            this.update(cx, |this, cx| {
2761                let state = this
2762                    .follower_states_by_leader
2763                    .get_mut(&leader_id)?
2764                    .get_mut(&pane)?;
2765
2766                for (id, item) in leader_view_ids.into_iter().zip(items) {
2767                    item.set_leader_replica_id(Some(replica_id), cx);
2768                    state.items_by_leader_view_id.insert(id, item);
2769                }
2770
2771                Some(())
2772            })?;
2773        }
2774        Ok(())
2775    }
2776
2777    fn update_active_view_for_followers(&self, cx: &AppContext) {
2778        if self.active_pane.read(cx).has_focus() {
2779            self.update_followers(
2780                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2781                    id: self.active_item(cx).and_then(|item| {
2782                        item.to_followable_item_handle(cx)?
2783                            .remote_id(&self.app_state.client, cx)
2784                            .map(|id| id.to_proto())
2785                    }),
2786                    leader_id: self.leader_for_pane(&self.active_pane),
2787                }),
2788                cx,
2789            );
2790        } else {
2791            self.update_followers(
2792                proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView {
2793                    id: None,
2794                    leader_id: None,
2795                }),
2796                cx,
2797            );
2798        }
2799    }
2800
2801    fn update_followers(
2802        &self,
2803        update: proto::update_followers::Variant,
2804        cx: &AppContext,
2805    ) -> Option<()> {
2806        let project_id = self.project.read(cx).remote_id()?;
2807        if !self.leader_state.followers.is_empty() {
2808            self.app_state
2809                .client
2810                .send(proto::UpdateFollowers {
2811                    project_id,
2812                    follower_ids: self.leader_state.followers.iter().copied().collect(),
2813                    variant: Some(update),
2814                })
2815                .log_err();
2816        }
2817        None
2818    }
2819
2820    pub fn leader_for_pane(&self, pane: &ViewHandle<Pane>) -> Option<PeerId> {
2821        self.follower_states_by_leader
2822            .iter()
2823            .find_map(|(leader_id, state)| {
2824                if state.contains_key(pane) {
2825                    Some(*leader_id)
2826                } else {
2827                    None
2828                }
2829            })
2830    }
2831
2832    fn leader_updated(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Option<()> {
2833        cx.notify();
2834
2835        let call = self.active_call()?;
2836        let room = call.read(cx).room()?.read(cx);
2837        let participant = room.remote_participant_for_peer_id(leader_id)?;
2838        let mut items_to_activate = Vec::new();
2839        match participant.location {
2840            call::ParticipantLocation::SharedProject { project_id } => {
2841                if Some(project_id) == self.project.read(cx).remote_id() {
2842                    for (pane, state) in self.follower_states_by_leader.get(&leader_id)? {
2843                        if let Some(item) = state
2844                            .active_view_id
2845                            .and_then(|id| state.items_by_leader_view_id.get(&id))
2846                        {
2847                            items_to_activate.push((pane.clone(), item.boxed_clone()));
2848                        } else if let Some(shared_screen) =
2849                            self.shared_screen_for_peer(leader_id, pane, cx)
2850                        {
2851                            items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2852                        }
2853                    }
2854                }
2855            }
2856            call::ParticipantLocation::UnsharedProject => {}
2857            call::ParticipantLocation::External => {
2858                for (pane, _) in self.follower_states_by_leader.get(&leader_id)? {
2859                    if let Some(shared_screen) = self.shared_screen_for_peer(leader_id, pane, cx) {
2860                        items_to_activate.push((pane.clone(), Box::new(shared_screen)));
2861                    }
2862                }
2863            }
2864        }
2865
2866        for (pane, item) in items_to_activate {
2867            let pane_was_focused = pane.read(cx).has_focus();
2868            if let Some(index) = pane.update(cx, |pane, _| pane.index_for_item(item.as_ref())) {
2869                pane.update(cx, |pane, cx| pane.activate_item(index, false, false, cx));
2870            } else {
2871                pane.update(cx, |pane, cx| {
2872                    pane.add_item(item.boxed_clone(), false, false, None, cx)
2873                });
2874            }
2875
2876            if pane_was_focused {
2877                pane.update(cx, |pane, cx| pane.focus_active_item(cx));
2878            }
2879        }
2880
2881        None
2882    }
2883
2884    fn shared_screen_for_peer(
2885        &self,
2886        peer_id: PeerId,
2887        pane: &ViewHandle<Pane>,
2888        cx: &mut ViewContext<Self>,
2889    ) -> Option<ViewHandle<SharedScreen>> {
2890        let call = self.active_call()?;
2891        let room = call.read(cx).room()?.read(cx);
2892        let participant = room.remote_participant_for_peer_id(peer_id)?;
2893        let track = participant.video_tracks.values().next()?.clone();
2894        let user = participant.user.clone();
2895
2896        for item in pane.read(cx).items_of_type::<SharedScreen>() {
2897            if item.read(cx).peer_id == peer_id {
2898                return Some(item);
2899            }
2900        }
2901
2902        Some(cx.add_view(|cx| SharedScreen::new(&track, peer_id, user.clone(), cx)))
2903    }
2904
2905    pub fn on_window_activation_changed(&mut self, active: bool, cx: &mut ViewContext<Self>) {
2906        if active {
2907            cx.background()
2908                .spawn(persistence::DB.update_timestamp(self.database_id()))
2909                .detach();
2910        } else {
2911            for pane in &self.panes {
2912                pane.update(cx, |pane, cx| {
2913                    if let Some(item) = pane.active_item() {
2914                        item.workspace_deactivated(cx);
2915                    }
2916                    if matches!(
2917                        settings::get::<WorkspaceSettings>(cx).autosave,
2918                        AutosaveSetting::OnWindowChange | AutosaveSetting::OnFocusChange
2919                    ) {
2920                        for item in pane.items() {
2921                            Pane::autosave_item(item.as_ref(), self.project.clone(), cx)
2922                                .detach_and_log_err(cx);
2923                        }
2924                    }
2925                });
2926            }
2927        }
2928    }
2929
2930    fn active_call(&self) -> Option<&ModelHandle<ActiveCall>> {
2931        self.active_call.as_ref().map(|(call, _)| call)
2932    }
2933
2934    fn on_active_call_event(
2935        &mut self,
2936        _: ModelHandle<ActiveCall>,
2937        event: &call::room::Event,
2938        cx: &mut ViewContext<Self>,
2939    ) {
2940        match event {
2941            call::room::Event::ParticipantLocationChanged { participant_id }
2942            | call::room::Event::RemoteVideoTracksChanged { participant_id } => {
2943                self.leader_updated(*participant_id, cx);
2944            }
2945            _ => {}
2946        }
2947    }
2948
2949    pub fn database_id(&self) -> WorkspaceId {
2950        self.database_id
2951    }
2952
2953    fn location(&self, cx: &AppContext) -> Option<WorkspaceLocation> {
2954        let project = self.project().read(cx);
2955
2956        if project.is_local() {
2957            Some(
2958                project
2959                    .visible_worktrees(cx)
2960                    .map(|worktree| worktree.read(cx).abs_path())
2961                    .collect::<Vec<_>>()
2962                    .into(),
2963            )
2964        } else {
2965            None
2966        }
2967    }
2968
2969    fn remove_panes(&mut self, member: Member, cx: &mut ViewContext<Workspace>) {
2970        match member {
2971            Member::Axis(PaneAxis { members, .. }) => {
2972                for child in members.iter() {
2973                    self.remove_panes(child.clone(), cx)
2974                }
2975            }
2976            Member::Pane(pane) => {
2977                self.force_remove_pane(&pane, cx);
2978            }
2979        }
2980    }
2981
2982    fn force_remove_pane(&mut self, pane: &ViewHandle<Pane>, cx: &mut ViewContext<Workspace>) {
2983        self.panes.retain(|p| p != pane);
2984        cx.focus(self.panes.last().unwrap());
2985        if self.last_active_center_pane == Some(pane.downgrade()) {
2986            self.last_active_center_pane = None;
2987        }
2988        cx.notify();
2989    }
2990
2991    fn schedule_serialize(&mut self, cx: &mut ViewContext<Self>) {
2992        self._schedule_serialize = Some(cx.spawn(|this, cx| async move {
2993            cx.background().timer(Duration::from_millis(100)).await;
2994            this.read_with(&cx, |this, cx| this.serialize_workspace(cx))
2995                .ok();
2996        }));
2997    }
2998
2999    fn serialize_workspace(&self, cx: &ViewContext<Self>) {
3000        fn serialize_pane_handle(
3001            pane_handle: &ViewHandle<Pane>,
3002            cx: &AppContext,
3003        ) -> SerializedPane {
3004            let (items, active) = {
3005                let pane = pane_handle.read(cx);
3006                let active_item_id = pane.active_item().map(|item| item.id());
3007                (
3008                    pane.items()
3009                        .filter_map(|item_handle| {
3010                            Some(SerializedItem {
3011                                kind: Arc::from(item_handle.serialized_item_kind()?),
3012                                item_id: item_handle.id(),
3013                                active: Some(item_handle.id()) == active_item_id,
3014                            })
3015                        })
3016                        .collect::<Vec<_>>(),
3017                    pane.has_focus(),
3018                )
3019            };
3020
3021            SerializedPane::new(items, active)
3022        }
3023
3024        fn build_serialized_pane_group(
3025            pane_group: &Member,
3026            cx: &AppContext,
3027        ) -> SerializedPaneGroup {
3028            match pane_group {
3029                Member::Axis(PaneAxis {
3030                    axis,
3031                    members,
3032                    flexes,
3033                }) => SerializedPaneGroup::Group {
3034                    axis: *axis,
3035                    children: members
3036                        .iter()
3037                        .map(|member| build_serialized_pane_group(member, cx))
3038                        .collect::<Vec<_>>(),
3039                    flexes: Some(flexes.borrow().clone()),
3040                },
3041                Member::Pane(pane_handle) => {
3042                    SerializedPaneGroup::Pane(serialize_pane_handle(&pane_handle, cx))
3043                }
3044            }
3045        }
3046
3047        fn build_serialized_docks(this: &Workspace, cx: &ViewContext<Workspace>) -> DockStructure {
3048            let left_dock = this.left_dock.read(cx);
3049            let left_visible = left_dock.is_open();
3050            let left_active_panel = left_dock.visible_panel().and_then(|panel| {
3051                Some(
3052                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
3053                        .to_string(),
3054                )
3055            });
3056            let left_dock_zoom = left_dock
3057                .visible_panel()
3058                .map(|panel| panel.is_zoomed(cx))
3059                .unwrap_or(false);
3060
3061            let right_dock = this.right_dock.read(cx);
3062            let right_visible = right_dock.is_open();
3063            let right_active_panel = right_dock.visible_panel().and_then(|panel| {
3064                Some(
3065                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
3066                        .to_string(),
3067                )
3068            });
3069            let right_dock_zoom = right_dock
3070                .visible_panel()
3071                .map(|panel| panel.is_zoomed(cx))
3072                .unwrap_or(false);
3073
3074            let bottom_dock = this.bottom_dock.read(cx);
3075            let bottom_visible = bottom_dock.is_open();
3076            let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| {
3077                Some(
3078                    cx.view_ui_name(panel.as_any().window_id(), panel.id())?
3079                        .to_string(),
3080                )
3081            });
3082            let bottom_dock_zoom = bottom_dock
3083                .visible_panel()
3084                .map(|panel| panel.is_zoomed(cx))
3085                .unwrap_or(false);
3086
3087            DockStructure {
3088                left: DockData {
3089                    visible: left_visible,
3090                    active_panel: left_active_panel,
3091                    zoom: left_dock_zoom,
3092                },
3093                right: DockData {
3094                    visible: right_visible,
3095                    active_panel: right_active_panel,
3096                    zoom: right_dock_zoom,
3097                },
3098                bottom: DockData {
3099                    visible: bottom_visible,
3100                    active_panel: bottom_active_panel,
3101                    zoom: bottom_dock_zoom,
3102                },
3103            }
3104        }
3105
3106        if let Some(location) = self.location(cx) {
3107            // Load bearing special case:
3108            //  - with_local_workspace() relies on this to not have other stuff open
3109            //    when you open your log
3110            if !location.paths().is_empty() {
3111                let center_group = build_serialized_pane_group(&self.center.root, cx);
3112                let docks = build_serialized_docks(self, cx);
3113
3114                let serialized_workspace = SerializedWorkspace {
3115                    id: self.database_id,
3116                    location,
3117                    center_group,
3118                    bounds: Default::default(),
3119                    display: Default::default(),
3120                    docks,
3121                };
3122
3123                cx.background()
3124                    .spawn(persistence::DB.save_workspace(serialized_workspace))
3125                    .detach();
3126            }
3127        }
3128    }
3129
3130    pub(crate) fn load_workspace(
3131        workspace: WeakViewHandle<Workspace>,
3132        serialized_workspace: SerializedWorkspace,
3133        paths_to_open: Vec<Option<ProjectPath>>,
3134        cx: &mut AppContext,
3135    ) -> Task<Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>> {
3136        cx.spawn(|mut cx| async move {
3137            let result = async_iife! {{
3138                let (project, old_center_pane) =
3139                workspace.read_with(&cx, |workspace, _| {
3140                    (
3141                        workspace.project().clone(),
3142                        workspace.last_active_center_pane.clone(),
3143                    )
3144                })?;
3145
3146                let mut center_items = None;
3147                let mut center_group = None;
3148                // Traverse the splits tree and add to things
3149                if let Some((group, active_pane, items)) = serialized_workspace
3150                        .center_group
3151                        .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
3152                        .await {
3153                    center_items = Some(items);
3154                    center_group = Some((group, active_pane))
3155                }
3156
3157                let resulting_list = cx.read(|cx| {
3158                    let mut opened_items = center_items
3159                        .unwrap_or_default()
3160                        .into_iter()
3161                        .filter_map(|item| {
3162                            let item = item?;
3163                            let project_path = item.project_path(cx)?;
3164                            Some((project_path, item))
3165                        })
3166                        .collect::<HashMap<_, _>>();
3167
3168                    paths_to_open
3169                        .into_iter()
3170                        .map(|path_to_open| {
3171                            path_to_open.map(|path_to_open| {
3172                                Ok(opened_items.remove(&path_to_open))
3173                            })
3174                            .transpose()
3175                            .map(|item| item.flatten())
3176                            .transpose()
3177                        })
3178                        .collect::<Vec<_>>()
3179                });
3180
3181                // Remove old panes from workspace panes list
3182                workspace.update(&mut cx, |workspace, cx| {
3183                    if let Some((center_group, active_pane)) = center_group {
3184                        workspace.remove_panes(workspace.center.root.clone(), cx);
3185
3186                        // Swap workspace center group
3187                        workspace.center = PaneGroup::with_root(center_group);
3188
3189                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
3190                        cx.focus_self();
3191
3192                        if let Some(active_pane) = active_pane {
3193                            cx.focus(&active_pane);
3194                        } else {
3195                            cx.focus(workspace.panes.last().unwrap());
3196                        }
3197                    } else {
3198                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
3199                        if let Some(old_center_handle) = old_center_handle {
3200                            cx.focus(&old_center_handle)
3201                        } else {
3202                            cx.focus_self()
3203                        }
3204                    }
3205
3206                    let docks = serialized_workspace.docks;
3207                    workspace.left_dock.update(cx, |dock, cx| {
3208                        dock.set_open(docks.left.visible, cx);
3209                        if let Some(active_panel) = docks.left.active_panel {
3210                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3211                                dock.activate_panel(ix, cx);
3212                            }
3213                        }
3214                                dock.active_panel()
3215                                    .map(|panel| {
3216                                        panel.set_zoomed(docks.left.zoom, cx)
3217                                    });
3218                                if docks.left.visible && docks.left.zoom {
3219                                    cx.focus_self()
3220                                }
3221                    });
3222                    // TODO: I think the bug is that setting zoom or active undoes the bottom zoom or something
3223                    workspace.right_dock.update(cx, |dock, cx| {
3224                        dock.set_open(docks.right.visible, cx);
3225                        if let Some(active_panel) = docks.right.active_panel {
3226                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3227                                dock.activate_panel(ix, cx);
3228
3229                            }
3230                        }
3231                                dock.active_panel()
3232                                    .map(|panel| {
3233                                        panel.set_zoomed(docks.right.zoom, cx)
3234                                    });
3235
3236                                if docks.right.visible && docks.right.zoom {
3237                                    cx.focus_self()
3238                                }
3239                    });
3240                    workspace.bottom_dock.update(cx, |dock, cx| {
3241                        dock.set_open(docks.bottom.visible, cx);
3242                        if let Some(active_panel) = docks.bottom.active_panel {
3243                            if let Some(ix) = dock.panel_index_for_ui_name(&active_panel, cx) {
3244                                dock.activate_panel(ix, cx);
3245                            }
3246                        }
3247
3248                        dock.active_panel()
3249                            .map(|panel| {
3250                                panel.set_zoomed(docks.bottom.zoom, cx)
3251                            });
3252
3253                        if docks.bottom.visible && docks.bottom.zoom {
3254                            cx.focus_self()
3255                        }
3256                    });
3257
3258
3259                    cx.notify();
3260                })?;
3261
3262                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
3263                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
3264
3265                Ok::<_, anyhow::Error>(resulting_list)
3266            }};
3267
3268            result.await.unwrap_or_default()
3269        })
3270    }
3271
3272    #[cfg(any(test, feature = "test-support"))]
3273    pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
3274        let app_state = Arc::new(AppState {
3275            languages: project.read(cx).languages().clone(),
3276            client: project.read(cx).client(),
3277            user_store: project.read(cx).user_store(),
3278            fs: project.read(cx).fs().clone(),
3279            build_window_options: |_, _, _| Default::default(),
3280            initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
3281            background_actions: || &[],
3282        });
3283        Self::new(0, project, app_state, cx)
3284    }
3285
3286    fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option<AnyElement<Self>> {
3287        let dock = match position {
3288            DockPosition::Left => &self.left_dock,
3289            DockPosition::Right => &self.right_dock,
3290            DockPosition::Bottom => &self.bottom_dock,
3291        };
3292        let active_panel = dock.read(cx).visible_panel()?;
3293        let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) {
3294            dock.read(cx).render_placeholder(cx)
3295        } else {
3296            ChildView::new(dock, cx).into_any()
3297        };
3298
3299        Some(
3300            element
3301                .constrained()
3302                .dynamically(move |constraint, _, cx| match position {
3303                    DockPosition::Left | DockPosition::Right => SizeConstraint::new(
3304                        Vector2F::new(20., constraint.min.y()),
3305                        Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()),
3306                    ),
3307                    DockPosition::Bottom => SizeConstraint::new(
3308                        Vector2F::new(constraint.min.x(), 20.),
3309                        Vector2F::new(constraint.max.x(), cx.window_size().y() * 0.8),
3310                    ),
3311                })
3312                .into_any(),
3313        )
3314    }
3315}
3316
3317fn window_bounds_env_override(cx: &AsyncAppContext) -> Option<WindowBounds> {
3318    ZED_WINDOW_POSITION
3319        .zip(*ZED_WINDOW_SIZE)
3320        .map(|(position, size)| {
3321            WindowBounds::Fixed(RectF::new(
3322                cx.platform().screens()[0].bounds().origin() + position,
3323                size,
3324            ))
3325        })
3326}
3327
3328async fn open_items(
3329    serialized_workspace: Option<SerializedWorkspace>,
3330    workspace: &WeakViewHandle<Workspace>,
3331    mut project_paths_to_open: Vec<(PathBuf, Option<ProjectPath>)>,
3332    app_state: Arc<AppState>,
3333    mut cx: AsyncAppContext,
3334) -> Vec<Option<anyhow::Result<Box<dyn ItemHandle>>>> {
3335    let mut opened_items = Vec::with_capacity(project_paths_to_open.len());
3336
3337    if let Some(serialized_workspace) = serialized_workspace {
3338        let workspace = workspace.clone();
3339        let restored_items = cx
3340            .update(|cx| {
3341                Workspace::load_workspace(
3342                    workspace,
3343                    serialized_workspace,
3344                    project_paths_to_open
3345                        .iter()
3346                        .map(|(_, project_path)| project_path)
3347                        .cloned()
3348                        .collect(),
3349                    cx,
3350                )
3351            })
3352            .await;
3353
3354        let restored_project_paths = cx.read(|cx| {
3355            restored_items
3356                .iter()
3357                .filter_map(|item| item.as_ref()?.as_ref().ok()?.project_path(cx))
3358                .collect::<HashSet<_>>()
3359        });
3360
3361        opened_items = restored_items;
3362        project_paths_to_open
3363            .iter_mut()
3364            .for_each(|(_, project_path)| {
3365                if let Some(project_path_to_open) = project_path {
3366                    if restored_project_paths.contains(project_path_to_open) {
3367                        *project_path = None;
3368                    }
3369                }
3370            });
3371    } else {
3372        for _ in 0..project_paths_to_open.len() {
3373            opened_items.push(None);
3374        }
3375    }
3376    assert!(opened_items.len() == project_paths_to_open.len());
3377
3378    let tasks =
3379        project_paths_to_open
3380            .into_iter()
3381            .enumerate()
3382            .map(|(i, (abs_path, project_path))| {
3383                let workspace = workspace.clone();
3384                cx.spawn(|mut cx| {
3385                    let fs = app_state.fs.clone();
3386                    async move {
3387                        let file_project_path = project_path?;
3388                        if fs.is_file(&abs_path).await {
3389                            Some((
3390                                i,
3391                                workspace
3392                                    .update(&mut cx, |workspace, cx| {
3393                                        workspace.open_path(file_project_path, None, true, cx)
3394                                    })
3395                                    .log_err()?
3396                                    .await,
3397                            ))
3398                        } else {
3399                            None
3400                        }
3401                    }
3402                })
3403            });
3404
3405    for maybe_opened_path in futures::future::join_all(tasks.into_iter())
3406        .await
3407        .into_iter()
3408    {
3409        if let Some((i, path_open_result)) = maybe_opened_path {
3410            opened_items[i] = Some(path_open_result);
3411        }
3412    }
3413
3414    opened_items
3415}
3416
3417fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3418    const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system";
3419    const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key";
3420    const MESSAGE_ID: usize = 2;
3421
3422    if workspace
3423        .read_with(cx, |workspace, cx| {
3424            workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3425        })
3426        .unwrap_or(false)
3427    {
3428        return;
3429    }
3430
3431    if db::kvp::KEY_VALUE_STORE
3432        .read_kvp(NEW_DOCK_HINT_KEY)
3433        .ok()
3434        .flatten()
3435        .is_some()
3436    {
3437        if !workspace
3438            .read_with(cx, |workspace, cx| {
3439                workspace.has_shown_notification_once::<MessageNotification>(MESSAGE_ID, cx)
3440            })
3441            .unwrap_or(false)
3442        {
3443            cx.update(|cx| {
3444                cx.update_global::<NotificationTracker, _, _>(|tracker, _| {
3445                    let entry = tracker
3446                        .entry(TypeId::of::<MessageNotification>())
3447                        .or_default();
3448                    if !entry.contains(&MESSAGE_ID) {
3449                        entry.push(MESSAGE_ID);
3450                    }
3451                });
3452            });
3453        }
3454
3455        return;
3456    }
3457
3458    cx.spawn(|_| async move {
3459        db::kvp::KEY_VALUE_STORE
3460            .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string())
3461            .await
3462            .ok();
3463    })
3464    .detach();
3465
3466    workspace
3467        .update(cx, |workspace, cx| {
3468            workspace.show_notification_once(2, cx, |cx| {
3469                cx.add_view(|_| {
3470                    MessageNotification::new_element(|text, _| {
3471                        Text::new(
3472                            "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
3473                            text,
3474                        )
3475                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| {
3476                            let code_span_background_color = settings::get::<ThemeSettings>(cx)
3477                                .theme
3478                                .editor
3479                                .document_highlight_read_background;
3480
3481                            scene.push_quad(gpui::Quad {
3482                                bounds,
3483                                background: Some(code_span_background_color),
3484                                border: Default::default(),
3485                                corner_radius: 2.0,
3486                            })
3487                        })
3488                        .into_any()
3489                    })
3490                    .with_click_message("Read more about the new panel system")
3491                    .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST))
3492                })
3493            })
3494        })
3495        .ok();
3496}
3497
3498fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
3499    const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml";
3500
3501    workspace
3502        .update(cx, |workspace, cx| {
3503            if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
3504                workspace.show_notification_once(0, cx, |cx| {
3505                    cx.add_view(|_| {
3506                        MessageNotification::new("Failed to load the database file.")
3507                            .with_click_message("Click to let us know about this error")
3508                            .on_click(|cx| cx.platform().open_url(REPORT_ISSUE_URL))
3509                    })
3510                });
3511            }
3512        })
3513        .log_err();
3514}
3515
3516impl Entity for Workspace {
3517    type Event = Event;
3518}
3519
3520impl View for Workspace {
3521    fn ui_name() -> &'static str {
3522        "Workspace"
3523    }
3524
3525    fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
3526        let theme = theme::current(cx).clone();
3527        Stack::new()
3528            .with_child(
3529                Flex::column()
3530                    .with_child(self.render_titlebar(&theme, cx))
3531                    .with_child(
3532                        Stack::new()
3533                            .with_child({
3534                                let project = self.project.clone();
3535                                Flex::row()
3536                                    .with_children(self.render_dock(DockPosition::Left, cx))
3537                                    .with_child(
3538                                        Flex::column()
3539                                            .with_child(
3540                                                FlexItem::new(
3541                                                    self.center.render(
3542                                                        &project,
3543                                                        &theme,
3544                                                        &self.follower_states_by_leader,
3545                                                        self.active_call(),
3546                                                        self.active_pane(),
3547                                                        self.zoomed
3548                                                            .as_ref()
3549                                                            .and_then(|zoomed| zoomed.upgrade(cx))
3550                                                            .as_ref(),
3551                                                        &self.app_state,
3552                                                        cx,
3553                                                    ),
3554                                                )
3555                                                .flex(1., true),
3556                                            )
3557                                            .with_children(
3558                                                self.render_dock(DockPosition::Bottom, cx),
3559                                            )
3560                                            .flex(1., true),
3561                                    )
3562                                    .with_children(self.render_dock(DockPosition::Right, cx))
3563                            })
3564                            .with_child(Overlay::new(
3565                                Stack::new()
3566                                    .with_children(self.zoomed.as_ref().and_then(|zoomed| {
3567                                        enum ZoomBackground {}
3568                                        let zoomed = zoomed.upgrade(cx)?;
3569
3570                                        let mut foreground_style =
3571                                            theme.workspace.zoomed_pane_foreground;
3572                                        if let Some(zoomed_dock_position) = self.zoomed_position {
3573                                            foreground_style =
3574                                                theme.workspace.zoomed_panel_foreground;
3575                                            let margin = foreground_style.margin.top;
3576                                            let border = foreground_style.border.top;
3577
3578                                            // Only include a margin and border on the opposite side.
3579                                            foreground_style.margin.top = 0.;
3580                                            foreground_style.margin.left = 0.;
3581                                            foreground_style.margin.bottom = 0.;
3582                                            foreground_style.margin.right = 0.;
3583                                            foreground_style.border.top = false;
3584                                            foreground_style.border.left = false;
3585                                            foreground_style.border.bottom = false;
3586                                            foreground_style.border.right = false;
3587                                            match zoomed_dock_position {
3588                                                DockPosition::Left => {
3589                                                    foreground_style.margin.right = margin;
3590                                                    foreground_style.border.right = border;
3591                                                }
3592                                                DockPosition::Right => {
3593                                                    foreground_style.margin.left = margin;
3594                                                    foreground_style.border.left = border;
3595                                                }
3596                                                DockPosition::Bottom => {
3597                                                    foreground_style.margin.top = margin;
3598                                                    foreground_style.border.top = border;
3599                                                }
3600                                            }
3601                                        }
3602
3603                                        Some(
3604                                            ChildView::new(&zoomed, cx)
3605                                                .contained()
3606                                                .with_style(foreground_style)
3607                                                .aligned()
3608                                                .contained()
3609                                                .with_style(theme.workspace.zoomed_background)
3610                                                .mouse::<ZoomBackground>(0)
3611                                                .capture_all()
3612                                                .on_down(
3613                                                    MouseButton::Left,
3614                                                    |_, this: &mut Self, cx| {
3615                                                        this.zoom_out(cx);
3616                                                    },
3617                                                ),
3618                                        )
3619                                    }))
3620                                    .with_children(self.modal.as_ref().map(|modal| {
3621                                        ChildView::new(modal.view.as_any(), cx)
3622                                            .contained()
3623                                            .with_style(theme.workspace.modal)
3624                                            .aligned()
3625                                            .top()
3626                                    }))
3627                                    .with_children(self.render_notifications(&theme.workspace, cx)),
3628                            ))
3629                            .flex(1.0, true),
3630                    )
3631                    .with_child(ChildView::new(&self.status_bar, cx))
3632                    .contained()
3633                    .with_background_color(theme.workspace.background),
3634            )
3635            .with_children(DragAndDrop::render(cx))
3636            .with_children(self.render_disconnected_overlay(cx))
3637            .into_any_named("workspace")
3638    }
3639
3640    fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
3641        if cx.is_self_focused() {
3642            cx.focus(&self.active_pane);
3643        }
3644    }
3645}
3646
3647impl ViewId {
3648    pub(crate) fn from_proto(message: proto::ViewId) -> Result<Self> {
3649        Ok(Self {
3650            creator: message
3651                .creator
3652                .ok_or_else(|| anyhow!("creator is missing"))?,
3653            id: message.id,
3654        })
3655    }
3656
3657    pub(crate) fn to_proto(&self) -> proto::ViewId {
3658        proto::ViewId {
3659            creator: Some(self.creator),
3660            id: self.id,
3661        }
3662    }
3663}
3664
3665pub trait WorkspaceHandle {
3666    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
3667}
3668
3669impl WorkspaceHandle for ViewHandle<Workspace> {
3670    fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
3671        self.read(cx)
3672            .worktrees(cx)
3673            .flat_map(|worktree| {
3674                let worktree_id = worktree.read(cx).id();
3675                worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
3676                    worktree_id,
3677                    path: f.path.clone(),
3678                })
3679            })
3680            .collect::<Vec<_>>()
3681    }
3682}
3683
3684impl std::fmt::Debug for OpenPaths {
3685    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3686        f.debug_struct("OpenPaths")
3687            .field("paths", &self.paths)
3688            .finish()
3689    }
3690}
3691
3692pub struct WorkspaceCreated(pub WeakViewHandle<Workspace>);
3693
3694pub fn activate_workspace_for_project(
3695    cx: &mut AsyncAppContext,
3696    predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
3697) -> Option<WeakViewHandle<Workspace>> {
3698    for window_id in cx.window_ids() {
3699        let handle = cx
3700            .update_window(window_id, |cx| {
3701                if let Some(workspace_handle) = cx.root_view().clone().downcast::<Workspace>() {
3702                    let project = workspace_handle.read(cx).project.clone();
3703                    if project.update(cx, &predicate) {
3704                        cx.activate_window();
3705                        return Some(workspace_handle.clone());
3706                    }
3707                }
3708                None
3709            })
3710            .flatten();
3711
3712        if let Some(handle) = handle {
3713            return Some(handle.downgrade());
3714        }
3715    }
3716    None
3717}
3718
3719pub async fn last_opened_workspace_paths() -> Option<WorkspaceLocation> {
3720    DB.last_workspace().await.log_err().flatten()
3721}
3722
3723#[allow(clippy::type_complexity)]
3724pub fn open_paths(
3725    abs_paths: &[PathBuf],
3726    app_state: &Arc<AppState>,
3727    requesting_window_id: Option<usize>,
3728    cx: &mut AppContext,
3729) -> Task<
3730    Result<(
3731        WeakViewHandle<Workspace>,
3732        Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
3733    )>,
3734> {
3735    let app_state = app_state.clone();
3736    let abs_paths = abs_paths.to_vec();
3737    cx.spawn(|mut cx| async move {
3738        // Open paths in existing workspace if possible
3739        let existing = activate_workspace_for_project(&mut cx, |project, cx| {
3740            project.contains_paths(&abs_paths, cx)
3741        });
3742
3743        if let Some(existing) = existing {
3744            Ok((
3745                existing.clone(),
3746                existing
3747                    .update(&mut cx, |workspace, cx| {
3748                        workspace.open_paths(abs_paths, true, cx)
3749                    })?
3750                    .await,
3751            ))
3752        } else {
3753            Ok(cx
3754                .update(|cx| {
3755                    Workspace::new_local(abs_paths, app_state.clone(), requesting_window_id, cx)
3756                })
3757                .await)
3758        }
3759    })
3760}
3761
3762pub fn open_new(
3763    app_state: &Arc<AppState>,
3764    cx: &mut AppContext,
3765    init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static,
3766) -> Task<()> {
3767    let task = Workspace::new_local(Vec::new(), app_state.clone(), None, cx);
3768    cx.spawn(|mut cx| async move {
3769        let (workspace, opened_paths) = task.await;
3770
3771        workspace
3772            .update(&mut cx, |workspace, cx| {
3773                if opened_paths.is_empty() {
3774                    init(workspace, cx)
3775                }
3776            })
3777            .log_err();
3778    })
3779}
3780
3781pub fn create_and_open_local_file(
3782    path: &'static Path,
3783    cx: &mut ViewContext<Workspace>,
3784    default_content: impl 'static + Send + FnOnce() -> Rope,
3785) -> Task<Result<Box<dyn ItemHandle>>> {
3786    cx.spawn(|workspace, mut cx| async move {
3787        let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
3788        if !fs.is_file(path).await {
3789            fs.create_file(path, Default::default()).await?;
3790            fs.save(path, &default_content(), Default::default())
3791                .await?;
3792        }
3793
3794        let mut items = workspace
3795            .update(&mut cx, |workspace, cx| {
3796                workspace.with_local_workspace(cx, |workspace, cx| {
3797                    workspace.open_paths(vec![path.to_path_buf()], false, cx)
3798                })
3799            })?
3800            .await?
3801            .await;
3802
3803        let item = items.pop().flatten();
3804        item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
3805    })
3806}
3807
3808pub fn join_remote_project(
3809    project_id: u64,
3810    follow_user_id: u64,
3811    app_state: Arc<AppState>,
3812    cx: &mut AppContext,
3813) -> Task<Result<()>> {
3814    cx.spawn(|mut cx| async move {
3815        let existing_workspace = cx
3816            .window_ids()
3817            .into_iter()
3818            .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
3819            .find(|workspace| {
3820                cx.read_window(workspace.window_id(), |cx| {
3821                    workspace.read(cx).project().read(cx).remote_id() == Some(project_id)
3822                })
3823                .unwrap_or(false)
3824            });
3825
3826        let workspace = if let Some(existing_workspace) = existing_workspace {
3827            existing_workspace.downgrade()
3828        } else {
3829            let active_call = cx.read(ActiveCall::global);
3830            let room = active_call
3831                .read_with(&cx, |call, _| call.room().cloned())
3832                .ok_or_else(|| anyhow!("not in a call"))?;
3833            let project = room
3834                .update(&mut cx, |room, cx| {
3835                    room.join_project(
3836                        project_id,
3837                        app_state.languages.clone(),
3838                        app_state.fs.clone(),
3839                        cx,
3840                    )
3841                })
3842                .await?;
3843
3844            let window_bounds_override = window_bounds_env_override(&cx);
3845            let (_, workspace) = cx.add_window(
3846                (app_state.build_window_options)(
3847                    window_bounds_override,
3848                    None,
3849                    cx.platform().as_ref(),
3850                ),
3851                |cx| Workspace::new(0, project, app_state.clone(), cx),
3852            );
3853            (app_state.initialize_workspace)(
3854                workspace.downgrade(),
3855                false,
3856                app_state.clone(),
3857                cx.clone(),
3858            )
3859            .await
3860            .log_err();
3861
3862            workspace.downgrade()
3863        };
3864
3865        cx.activate_window(workspace.window_id());
3866        cx.platform().activate(true);
3867
3868        workspace.update(&mut cx, |workspace, cx| {
3869            if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
3870                let follow_peer_id = room
3871                    .read(cx)
3872                    .remote_participants()
3873                    .iter()
3874                    .find(|(_, participant)| participant.user.id == follow_user_id)
3875                    .map(|(_, p)| p.peer_id)
3876                    .or_else(|| {
3877                        // If we couldn't follow the given user, follow the host instead.
3878                        let collaborator = workspace
3879                            .project()
3880                            .read(cx)
3881                            .collaborators()
3882                            .values()
3883                            .find(|collaborator| collaborator.replica_id == 0)?;
3884                        Some(collaborator.peer_id)
3885                    });
3886
3887                if let Some(follow_peer_id) = follow_peer_id {
3888                    if !workspace.is_being_followed(follow_peer_id) {
3889                        workspace
3890                            .toggle_follow(follow_peer_id, cx)
3891                            .map(|follow| follow.detach_and_log_err(cx));
3892                    }
3893                }
3894            }
3895        })?;
3896
3897        anyhow::Ok(())
3898    })
3899}
3900
3901pub fn restart(_: &Restart, cx: &mut AppContext) {
3902    let should_confirm = settings::get::<WorkspaceSettings>(cx).confirm_quit;
3903    cx.spawn(|mut cx| async move {
3904        let mut workspaces = cx
3905            .window_ids()
3906            .into_iter()
3907            .filter_map(|window_id| {
3908                Some(
3909                    cx.root_view(window_id)?
3910                        .clone()
3911                        .downcast::<Workspace>()?
3912                        .downgrade(),
3913                )
3914            })
3915            .collect::<Vec<_>>();
3916
3917        // If multiple windows have unsaved changes, and need a save prompt,
3918        // prompt in the active window before switching to a different window.
3919        workspaces.sort_by_key(|workspace| !cx.window_is_active(workspace.window_id()));
3920
3921        if let (true, Some(workspace)) = (should_confirm, workspaces.first()) {
3922            let answer = cx.prompt(
3923                workspace.window_id(),
3924                PromptLevel::Info,
3925                "Are you sure you want to restart?",
3926                &["Restart", "Cancel"],
3927            );
3928
3929            if let Some(mut answer) = answer {
3930                let answer = answer.next().await;
3931                if answer != Some(0) {
3932                    return Ok(());
3933                }
3934            }
3935        }
3936
3937        // If the user cancels any save prompt, then keep the app open.
3938        for workspace in workspaces {
3939            if !workspace
3940                .update(&mut cx, |workspace, cx| {
3941                    workspace.prepare_to_close(true, cx)
3942                })?
3943                .await?
3944            {
3945                return Ok(());
3946            }
3947        }
3948        cx.platform().restart();
3949        anyhow::Ok(())
3950    })
3951    .detach_and_log_err(cx);
3952}
3953
3954fn parse_pixel_position_env_var(value: &str) -> Option<Vector2F> {
3955    let mut parts = value.split(',');
3956    let width: usize = parts.next()?.parse().ok()?;
3957    let height: usize = parts.next()?.parse().ok()?;
3958    Some(vec2f(width as f32, height as f32))
3959}
3960
3961#[cfg(test)]
3962mod tests {
3963    use super::*;
3964    use crate::{
3965        dock::test::{TestPanel, TestPanelEvent},
3966        item::test::{TestItem, TestItemEvent, TestProjectItem},
3967    };
3968    use fs::FakeFs;
3969    use gpui::{executor::Deterministic, test::EmptyView, TestAppContext};
3970    use project::{Project, ProjectEntryId};
3971    use serde_json::json;
3972    use settings::SettingsStore;
3973    use std::{cell::RefCell, rc::Rc};
3974
3975    #[gpui::test]
3976    async fn test_tab_disambiguation(cx: &mut TestAppContext) {
3977        init_test(cx);
3978
3979        let fs = FakeFs::new(cx.background());
3980        let project = Project::test(fs, [], cx).await;
3981        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
3982
3983        // Adding an item with no ambiguity renders the tab without detail.
3984        let item1 = cx.add_view(window_id, |_| {
3985            let mut item = TestItem::new();
3986            item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
3987            item
3988        });
3989        workspace.update(cx, |workspace, cx| {
3990            workspace.add_item(Box::new(item1.clone()), cx);
3991        });
3992        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
3993
3994        // Adding an item that creates ambiguity increases the level of detail on
3995        // both tabs.
3996        let item2 = cx.add_view(window_id, |_| {
3997            let mut item = TestItem::new();
3998            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
3999            item
4000        });
4001        workspace.update(cx, |workspace, cx| {
4002            workspace.add_item(Box::new(item2.clone()), cx);
4003        });
4004        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4005        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4006
4007        // Adding an item that creates ambiguity increases the level of detail only
4008        // on the ambiguous tabs. In this case, the ambiguity can't be resolved so
4009        // we stop at the highest detail available.
4010        let item3 = cx.add_view(window_id, |_| {
4011            let mut item = TestItem::new();
4012            item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
4013            item
4014        });
4015        workspace.update(cx, |workspace, cx| {
4016            workspace.add_item(Box::new(item3.clone()), cx);
4017        });
4018        item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
4019        item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4020        item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
4021    }
4022
4023    #[gpui::test]
4024    async fn test_tracking_active_path(cx: &mut TestAppContext) {
4025        init_test(cx);
4026
4027        let fs = FakeFs::new(cx.background());
4028        fs.insert_tree(
4029            "/root1",
4030            json!({
4031                "one.txt": "",
4032                "two.txt": "",
4033            }),
4034        )
4035        .await;
4036        fs.insert_tree(
4037            "/root2",
4038            json!({
4039                "three.txt": "",
4040            }),
4041        )
4042        .await;
4043
4044        let project = Project::test(fs, ["root1".as_ref()], cx).await;
4045        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4046        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4047        let worktree_id = project.read_with(cx, |project, cx| {
4048            project.worktrees(cx).next().unwrap().read(cx).id()
4049        });
4050
4051        let item1 = cx.add_view(window_id, |cx| {
4052            TestItem::new().with_project_items(&[TestProjectItem::new(1, "one.txt", cx)])
4053        });
4054        let item2 = cx.add_view(window_id, |cx| {
4055            TestItem::new().with_project_items(&[TestProjectItem::new(2, "two.txt", cx)])
4056        });
4057
4058        // Add an item to an empty pane
4059        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item1), cx));
4060        project.read_with(cx, |project, cx| {
4061            assert_eq!(
4062                project.active_entry(),
4063                project
4064                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4065                    .map(|e| e.id)
4066            );
4067        });
4068        assert_eq!(
4069            cx.current_window_title(window_id).as_deref(),
4070            Some("one.txt β€” root1")
4071        );
4072
4073        // Add a second item to a non-empty pane
4074        workspace.update(cx, |workspace, cx| workspace.add_item(Box::new(item2), cx));
4075        assert_eq!(
4076            cx.current_window_title(window_id).as_deref(),
4077            Some("two.txt β€” root1")
4078        );
4079        project.read_with(cx, |project, cx| {
4080            assert_eq!(
4081                project.active_entry(),
4082                project
4083                    .entry_for_path(&(worktree_id, "two.txt").into(), cx)
4084                    .map(|e| e.id)
4085            );
4086        });
4087
4088        // Close the active item
4089        pane.update(cx, |pane, cx| {
4090            pane.close_active_item(&Default::default(), cx).unwrap()
4091        })
4092        .await
4093        .unwrap();
4094        assert_eq!(
4095            cx.current_window_title(window_id).as_deref(),
4096            Some("one.txt β€” root1")
4097        );
4098        project.read_with(cx, |project, cx| {
4099            assert_eq!(
4100                project.active_entry(),
4101                project
4102                    .entry_for_path(&(worktree_id, "one.txt").into(), cx)
4103                    .map(|e| e.id)
4104            );
4105        });
4106
4107        // Add a project folder
4108        project
4109            .update(cx, |project, cx| {
4110                project.find_or_create_local_worktree("/root2", true, cx)
4111            })
4112            .await
4113            .unwrap();
4114        assert_eq!(
4115            cx.current_window_title(window_id).as_deref(),
4116            Some("one.txt β€” root1, root2")
4117        );
4118
4119        // Remove a project folder
4120        project.update(cx, |project, cx| project.remove_worktree(worktree_id, cx));
4121        assert_eq!(
4122            cx.current_window_title(window_id).as_deref(),
4123            Some("one.txt β€” root2")
4124        );
4125    }
4126
4127    #[gpui::test]
4128    async fn test_close_window(cx: &mut TestAppContext) {
4129        init_test(cx);
4130
4131        let fs = FakeFs::new(cx.background());
4132        fs.insert_tree("/root", json!({ "one": "" })).await;
4133
4134        let project = Project::test(fs, ["root".as_ref()], cx).await;
4135        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
4136
4137        // When there are no dirty items, there's nothing to do.
4138        let item1 = cx.add_view(window_id, |_| TestItem::new());
4139        workspace.update(cx, |w, cx| w.add_item(Box::new(item1.clone()), cx));
4140        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4141        assert!(task.await.unwrap());
4142
4143        // When there are dirty untitled items, prompt to save each one. If the user
4144        // cancels any prompt, then abort.
4145        let item2 = cx.add_view(window_id, |_| TestItem::new().with_dirty(true));
4146        let item3 = cx.add_view(window_id, |cx| {
4147            TestItem::new()
4148                .with_dirty(true)
4149                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4150        });
4151        workspace.update(cx, |w, cx| {
4152            w.add_item(Box::new(item2.clone()), cx);
4153            w.add_item(Box::new(item3.clone()), cx);
4154        });
4155        let task = workspace.update(cx, |w, cx| w.prepare_to_close(false, cx));
4156        cx.foreground().run_until_parked();
4157        cx.simulate_prompt_answer(window_id, 2 /* cancel */);
4158        cx.foreground().run_until_parked();
4159        assert!(!cx.has_pending_prompt(window_id));
4160        assert!(!task.await.unwrap());
4161    }
4162
4163    #[gpui::test]
4164    async fn test_close_pane_items(cx: &mut TestAppContext) {
4165        init_test(cx);
4166
4167        let fs = FakeFs::new(cx.background());
4168
4169        let project = Project::test(fs, None, cx).await;
4170        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4171
4172        let item1 = cx.add_view(window_id, |cx| {
4173            TestItem::new()
4174                .with_dirty(true)
4175                .with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4176        });
4177        let item2 = cx.add_view(window_id, |cx| {
4178            TestItem::new()
4179                .with_dirty(true)
4180                .with_conflict(true)
4181                .with_project_items(&[TestProjectItem::new(2, "2.txt", cx)])
4182        });
4183        let item3 = cx.add_view(window_id, |cx| {
4184            TestItem::new()
4185                .with_dirty(true)
4186                .with_conflict(true)
4187                .with_project_items(&[TestProjectItem::new(3, "3.txt", cx)])
4188        });
4189        let item4 = cx.add_view(window_id, |cx| {
4190            TestItem::new()
4191                .with_dirty(true)
4192                .with_project_items(&[TestProjectItem::new_untitled(cx)])
4193        });
4194        let pane = workspace.update(cx, |workspace, cx| {
4195            workspace.add_item(Box::new(item1.clone()), cx);
4196            workspace.add_item(Box::new(item2.clone()), cx);
4197            workspace.add_item(Box::new(item3.clone()), cx);
4198            workspace.add_item(Box::new(item4.clone()), cx);
4199            workspace.active_pane().clone()
4200        });
4201
4202        let close_items = pane.update(cx, |pane, cx| {
4203            pane.activate_item(1, true, true, cx);
4204            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4205            let item1_id = item1.id();
4206            let item3_id = item3.id();
4207            let item4_id = item4.id();
4208            pane.close_items(cx, move |id| [item1_id, item3_id, item4_id].contains(&id))
4209        });
4210        cx.foreground().run_until_parked();
4211
4212        // There's a prompt to save item 1.
4213        pane.read_with(cx, |pane, _| {
4214            assert_eq!(pane.items_len(), 4);
4215            assert_eq!(pane.active_item().unwrap().id(), item1.id());
4216        });
4217        assert!(cx.has_pending_prompt(window_id));
4218
4219        // Confirm saving item 1.
4220        cx.simulate_prompt_answer(window_id, 0);
4221        cx.foreground().run_until_parked();
4222
4223        // Item 1 is saved. There's a prompt to save item 3.
4224        pane.read_with(cx, |pane, cx| {
4225            assert_eq!(item1.read(cx).save_count, 1);
4226            assert_eq!(item1.read(cx).save_as_count, 0);
4227            assert_eq!(item1.read(cx).reload_count, 0);
4228            assert_eq!(pane.items_len(), 3);
4229            assert_eq!(pane.active_item().unwrap().id(), item3.id());
4230        });
4231        assert!(cx.has_pending_prompt(window_id));
4232
4233        // Cancel saving item 3.
4234        cx.simulate_prompt_answer(window_id, 1);
4235        cx.foreground().run_until_parked();
4236
4237        // Item 3 is reloaded. There's a prompt to save item 4.
4238        pane.read_with(cx, |pane, cx| {
4239            assert_eq!(item3.read(cx).save_count, 0);
4240            assert_eq!(item3.read(cx).save_as_count, 0);
4241            assert_eq!(item3.read(cx).reload_count, 1);
4242            assert_eq!(pane.items_len(), 2);
4243            assert_eq!(pane.active_item().unwrap().id(), item4.id());
4244        });
4245        assert!(cx.has_pending_prompt(window_id));
4246
4247        // Confirm saving item 4.
4248        cx.simulate_prompt_answer(window_id, 0);
4249        cx.foreground().run_until_parked();
4250
4251        // There's a prompt for a path for item 4.
4252        cx.simulate_new_path_selection(|_| Some(Default::default()));
4253        close_items.await.unwrap();
4254
4255        // The requested items are closed.
4256        pane.read_with(cx, |pane, cx| {
4257            assert_eq!(item4.read(cx).save_count, 0);
4258            assert_eq!(item4.read(cx).save_as_count, 1);
4259            assert_eq!(item4.read(cx).reload_count, 0);
4260            assert_eq!(pane.items_len(), 1);
4261            assert_eq!(pane.active_item().unwrap().id(), item2.id());
4262        });
4263    }
4264
4265    #[gpui::test]
4266    async fn test_prompting_to_save_only_on_last_item_for_entry(cx: &mut TestAppContext) {
4267        init_test(cx);
4268
4269        let fs = FakeFs::new(cx.background());
4270
4271        let project = Project::test(fs, [], cx).await;
4272        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4273
4274        // Create several workspace items with single project entries, and two
4275        // workspace items with multiple project entries.
4276        let single_entry_items = (0..=4)
4277            .map(|project_entry_id| {
4278                cx.add_view(window_id, |cx| {
4279                    TestItem::new()
4280                        .with_dirty(true)
4281                        .with_project_items(&[TestProjectItem::new(
4282                            project_entry_id,
4283                            &format!("{project_entry_id}.txt"),
4284                            cx,
4285                        )])
4286                })
4287            })
4288            .collect::<Vec<_>>();
4289        let item_2_3 = cx.add_view(window_id, |cx| {
4290            TestItem::new()
4291                .with_dirty(true)
4292                .with_singleton(false)
4293                .with_project_items(&[
4294                    single_entry_items[2].read(cx).project_items[0].clone(),
4295                    single_entry_items[3].read(cx).project_items[0].clone(),
4296                ])
4297        });
4298        let item_3_4 = cx.add_view(window_id, |cx| {
4299            TestItem::new()
4300                .with_dirty(true)
4301                .with_singleton(false)
4302                .with_project_items(&[
4303                    single_entry_items[3].read(cx).project_items[0].clone(),
4304                    single_entry_items[4].read(cx).project_items[0].clone(),
4305                ])
4306        });
4307
4308        // Create two panes that contain the following project entries:
4309        //   left pane:
4310        //     multi-entry items:   (2, 3)
4311        //     single-entry items:  0, 1, 2, 3, 4
4312        //   right pane:
4313        //     single-entry items:  1
4314        //     multi-entry items:   (3, 4)
4315        let left_pane = workspace.update(cx, |workspace, cx| {
4316            let left_pane = workspace.active_pane().clone();
4317            workspace.add_item(Box::new(item_2_3.clone()), cx);
4318            for item in single_entry_items {
4319                workspace.add_item(Box::new(item), cx);
4320            }
4321            left_pane.update(cx, |pane, cx| {
4322                pane.activate_item(2, true, true, cx);
4323            });
4324
4325            workspace
4326                .split_and_clone(left_pane.clone(), SplitDirection::Right, cx)
4327                .unwrap();
4328
4329            left_pane
4330        });
4331
4332        //Need to cause an effect flush in order to respect new focus
4333        workspace.update(cx, |workspace, cx| {
4334            workspace.add_item(Box::new(item_3_4.clone()), cx);
4335            cx.focus(&left_pane);
4336        });
4337
4338        // When closing all of the items in the left pane, we should be prompted twice:
4339        // once for project entry 0, and once for project entry 2. After those two
4340        // prompts, the task should complete.
4341
4342        let close = left_pane.update(cx, |pane, cx| pane.close_items(cx, |_| true));
4343        cx.foreground().run_until_parked();
4344        left_pane.read_with(cx, |pane, cx| {
4345            assert_eq!(
4346                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4347                &[ProjectEntryId::from_proto(0)]
4348            );
4349        });
4350        cx.simulate_prompt_answer(window_id, 0);
4351
4352        cx.foreground().run_until_parked();
4353        left_pane.read_with(cx, |pane, cx| {
4354            assert_eq!(
4355                pane.active_item().unwrap().project_entry_ids(cx).as_slice(),
4356                &[ProjectEntryId::from_proto(2)]
4357            );
4358        });
4359        cx.simulate_prompt_answer(window_id, 0);
4360
4361        cx.foreground().run_until_parked();
4362        close.await.unwrap();
4363        left_pane.read_with(cx, |pane, _| {
4364            assert_eq!(pane.items_len(), 0);
4365        });
4366    }
4367
4368    #[gpui::test]
4369    async fn test_autosave(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
4370        init_test(cx);
4371
4372        let fs = FakeFs::new(cx.background());
4373
4374        let project = Project::test(fs, [], cx).await;
4375        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4376        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4377
4378        let item = cx.add_view(window_id, |cx| {
4379            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4380        });
4381        let item_id = item.id();
4382        workspace.update(cx, |workspace, cx| {
4383            workspace.add_item(Box::new(item.clone()), cx);
4384        });
4385
4386        // Autosave on window change.
4387        item.update(cx, |item, cx| {
4388            cx.update_global(|settings: &mut SettingsStore, cx| {
4389                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4390                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
4391                })
4392            });
4393            item.is_dirty = true;
4394        });
4395
4396        // Deactivating the window saves the file.
4397        cx.simulate_window_activation(None);
4398        deterministic.run_until_parked();
4399        item.read_with(cx, |item, _| assert_eq!(item.save_count, 1));
4400
4401        // Autosave on focus change.
4402        item.update(cx, |item, cx| {
4403            cx.focus_self();
4404            cx.update_global(|settings: &mut SettingsStore, cx| {
4405                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4406                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4407                })
4408            });
4409            item.is_dirty = true;
4410        });
4411
4412        // Blurring the item saves the file.
4413        item.update(cx, |_, cx| cx.blur());
4414        deterministic.run_until_parked();
4415        item.read_with(cx, |item, _| assert_eq!(item.save_count, 2));
4416
4417        // Deactivating the window still saves the file.
4418        cx.simulate_window_activation(Some(window_id));
4419        item.update(cx, |item, cx| {
4420            cx.focus_self();
4421            item.is_dirty = true;
4422        });
4423        cx.simulate_window_activation(None);
4424
4425        deterministic.run_until_parked();
4426        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4427
4428        // Autosave after delay.
4429        item.update(cx, |item, cx| {
4430            cx.update_global(|settings: &mut SettingsStore, cx| {
4431                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4432                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
4433                })
4434            });
4435            item.is_dirty = true;
4436            cx.emit(TestItemEvent::Edit);
4437        });
4438
4439        // Delay hasn't fully expired, so the file is still dirty and unsaved.
4440        deterministic.advance_clock(Duration::from_millis(250));
4441        item.read_with(cx, |item, _| assert_eq!(item.save_count, 3));
4442
4443        // After delay expires, the file is saved.
4444        deterministic.advance_clock(Duration::from_millis(250));
4445        item.read_with(cx, |item, _| assert_eq!(item.save_count, 4));
4446
4447        // Autosave on focus change, ensuring closing the tab counts as such.
4448        item.update(cx, |item, cx| {
4449            cx.update_global(|settings: &mut SettingsStore, cx| {
4450                settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
4451                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
4452                })
4453            });
4454            item.is_dirty = true;
4455        });
4456
4457        pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id))
4458            .await
4459            .unwrap();
4460        assert!(!cx.has_pending_prompt(window_id));
4461        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4462
4463        // Add the item again, ensuring autosave is prevented if the underlying file has been deleted.
4464        workspace.update(cx, |workspace, cx| {
4465            workspace.add_item(Box::new(item.clone()), cx);
4466        });
4467        item.update(cx, |item, cx| {
4468            item.project_items[0].update(cx, |item, _| {
4469                item.entry_id = None;
4470            });
4471            item.is_dirty = true;
4472            cx.blur();
4473        });
4474        deterministic.run_until_parked();
4475        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4476
4477        // Ensure autosave is prevented for deleted files also when closing the buffer.
4478        let _close_items =
4479            pane.update(cx, |pane, cx| pane.close_items(cx, move |id| id == item_id));
4480        deterministic.run_until_parked();
4481        assert!(cx.has_pending_prompt(window_id));
4482        item.read_with(cx, |item, _| assert_eq!(item.save_count, 5));
4483    }
4484
4485    #[gpui::test]
4486    async fn test_pane_navigation(cx: &mut gpui::TestAppContext) {
4487        init_test(cx);
4488
4489        let fs = FakeFs::new(cx.background());
4490
4491        let project = Project::test(fs, [], cx).await;
4492        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4493
4494        let item = cx.add_view(window_id, |cx| {
4495            TestItem::new().with_project_items(&[TestProjectItem::new(1, "1.txt", cx)])
4496        });
4497        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4498        let toolbar = pane.read_with(cx, |pane, _| pane.toolbar().clone());
4499        let toolbar_notify_count = Rc::new(RefCell::new(0));
4500
4501        workspace.update(cx, |workspace, cx| {
4502            workspace.add_item(Box::new(item.clone()), cx);
4503            let toolbar_notification_count = toolbar_notify_count.clone();
4504            cx.observe(&toolbar, move |_, _, _| {
4505                *toolbar_notification_count.borrow_mut() += 1
4506            })
4507            .detach();
4508        });
4509
4510        pane.read_with(cx, |pane, _| {
4511            assert!(!pane.can_navigate_backward());
4512            assert!(!pane.can_navigate_forward());
4513        });
4514
4515        item.update(cx, |item, cx| {
4516            item.set_state("one".to_string(), cx);
4517        });
4518
4519        // Toolbar must be notified to re-render the navigation buttons
4520        assert_eq!(*toolbar_notify_count.borrow(), 1);
4521
4522        pane.read_with(cx, |pane, _| {
4523            assert!(pane.can_navigate_backward());
4524            assert!(!pane.can_navigate_forward());
4525        });
4526
4527        workspace
4528            .update(cx, |workspace, cx| workspace.go_back(pane.downgrade(), cx))
4529            .await
4530            .unwrap();
4531
4532        assert_eq!(*toolbar_notify_count.borrow(), 3);
4533        pane.read_with(cx, |pane, _| {
4534            assert!(!pane.can_navigate_backward());
4535            assert!(pane.can_navigate_forward());
4536        });
4537    }
4538
4539    #[gpui::test]
4540    async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) {
4541        init_test(cx);
4542        let fs = FakeFs::new(cx.background());
4543
4544        let project = Project::test(fs, [], cx).await;
4545        let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4546
4547        let panel = workspace.update(cx, |workspace, cx| {
4548            let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4549            workspace.add_panel(panel.clone(), cx);
4550
4551            workspace
4552                .right_dock()
4553                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4554
4555            panel
4556        });
4557
4558        let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
4559        pane.update(cx, |pane, cx| {
4560            let item = cx.add_view(|_| TestItem::new());
4561            pane.add_item(Box::new(item), true, true, None, cx);
4562        });
4563
4564        // Transfer focus from center to panel
4565        workspace.update(cx, |workspace, cx| {
4566            workspace.toggle_panel_focus::<TestPanel>(cx);
4567        });
4568
4569        workspace.read_with(cx, |workspace, cx| {
4570            assert!(workspace.right_dock().read(cx).is_open());
4571            assert!(!panel.is_zoomed(cx));
4572            assert!(panel.has_focus(cx));
4573        });
4574
4575        // Transfer focus from panel to center
4576        workspace.update(cx, |workspace, cx| {
4577            workspace.toggle_panel_focus::<TestPanel>(cx);
4578        });
4579
4580        workspace.read_with(cx, |workspace, cx| {
4581            assert!(workspace.right_dock().read(cx).is_open());
4582            assert!(!panel.is_zoomed(cx));
4583            assert!(!panel.has_focus(cx));
4584        });
4585
4586        // Close the dock
4587        workspace.update(cx, |workspace, cx| {
4588            workspace.toggle_dock(DockPosition::Right, cx);
4589        });
4590
4591        workspace.read_with(cx, |workspace, cx| {
4592            assert!(!workspace.right_dock().read(cx).is_open());
4593            assert!(!panel.is_zoomed(cx));
4594            assert!(!panel.has_focus(cx));
4595        });
4596
4597        // Open the dock
4598        workspace.update(cx, |workspace, cx| {
4599            workspace.toggle_dock(DockPosition::Right, cx);
4600        });
4601
4602        workspace.read_with(cx, |workspace, cx| {
4603            assert!(workspace.right_dock().read(cx).is_open());
4604            assert!(!panel.is_zoomed(cx));
4605            assert!(panel.has_focus(cx));
4606        });
4607
4608        // Focus and zoom panel
4609        panel.update(cx, |panel, cx| {
4610            cx.focus_self();
4611            panel.set_zoomed(true, cx)
4612        });
4613
4614        workspace.read_with(cx, |workspace, cx| {
4615            assert!(workspace.right_dock().read(cx).is_open());
4616            assert!(panel.is_zoomed(cx));
4617            assert!(panel.has_focus(cx));
4618        });
4619
4620        // Transfer focus to the center closes the dock
4621        workspace.update(cx, |workspace, cx| {
4622            workspace.toggle_panel_focus::<TestPanel>(cx);
4623        });
4624
4625        workspace.read_with(cx, |workspace, cx| {
4626            assert!(!workspace.right_dock().read(cx).is_open());
4627            assert!(panel.is_zoomed(cx));
4628            assert!(!panel.has_focus(cx));
4629        });
4630
4631        // Transferring focus back to the panel keeps it zoomed
4632        workspace.update(cx, |workspace, cx| {
4633            workspace.toggle_panel_focus::<TestPanel>(cx);
4634        });
4635
4636        workspace.read_with(cx, |workspace, cx| {
4637            assert!(workspace.right_dock().read(cx).is_open());
4638            assert!(panel.is_zoomed(cx));
4639            assert!(panel.has_focus(cx));
4640        });
4641
4642        // Close the dock while it is zoomed
4643        workspace.update(cx, |workspace, cx| {
4644            workspace.toggle_dock(DockPosition::Right, cx)
4645        });
4646
4647        workspace.read_with(cx, |workspace, cx| {
4648            assert!(!workspace.right_dock().read(cx).is_open());
4649            assert!(panel.is_zoomed(cx));
4650            assert!(workspace.zoomed.is_none());
4651            assert!(!panel.has_focus(cx));
4652        });
4653
4654        // Opening the dock, when it's zoomed, retains focus
4655        workspace.update(cx, |workspace, cx| {
4656            workspace.toggle_dock(DockPosition::Right, cx)
4657        });
4658
4659        workspace.read_with(cx, |workspace, cx| {
4660            assert!(workspace.right_dock().read(cx).is_open());
4661            assert!(panel.is_zoomed(cx));
4662            assert!(workspace.zoomed.is_some());
4663            assert!(panel.has_focus(cx));
4664        });
4665
4666        // Unzoom and close the panel, zoom the active pane.
4667        panel.update(cx, |panel, cx| panel.set_zoomed(false, cx));
4668        workspace.update(cx, |workspace, cx| {
4669            workspace.toggle_dock(DockPosition::Right, cx)
4670        });
4671        pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx));
4672
4673        // Opening a dock unzooms the pane.
4674        workspace.update(cx, |workspace, cx| {
4675            workspace.toggle_dock(DockPosition::Right, cx)
4676        });
4677        workspace.read_with(cx, |workspace, cx| {
4678            let pane = pane.read(cx);
4679            assert!(!pane.is_zoomed());
4680            assert!(!pane.has_focus());
4681            assert!(workspace.right_dock().read(cx).is_open());
4682            assert!(workspace.zoomed.is_none());
4683        });
4684    }
4685
4686    #[gpui::test]
4687    async fn test_panels(cx: &mut gpui::TestAppContext) {
4688        init_test(cx);
4689        let fs = FakeFs::new(cx.background());
4690
4691        let project = Project::test(fs, [], cx).await;
4692        let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx));
4693
4694        let (panel_1, panel_2) = workspace.update(cx, |workspace, cx| {
4695            // Add panel_1 on the left, panel_2 on the right.
4696            let panel_1 = cx.add_view(|_| TestPanel::new(DockPosition::Left));
4697            workspace.add_panel(panel_1.clone(), cx);
4698            workspace
4699                .left_dock()
4700                .update(cx, |left_dock, cx| left_dock.set_open(true, cx));
4701            let panel_2 = cx.add_view(|_| TestPanel::new(DockPosition::Right));
4702            workspace.add_panel(panel_2.clone(), cx);
4703            workspace
4704                .right_dock()
4705                .update(cx, |right_dock, cx| right_dock.set_open(true, cx));
4706
4707            let left_dock = workspace.left_dock();
4708            assert_eq!(
4709                left_dock.read(cx).visible_panel().unwrap().id(),
4710                panel_1.id()
4711            );
4712            assert_eq!(
4713                left_dock.read(cx).active_panel_size(cx).unwrap(),
4714                panel_1.size(cx)
4715            );
4716
4717            left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx));
4718            assert_eq!(
4719                workspace
4720                    .right_dock()
4721                    .read(cx)
4722                    .visible_panel()
4723                    .unwrap()
4724                    .id(),
4725                panel_2.id()
4726            );
4727
4728            (panel_1, panel_2)
4729        });
4730
4731        // Move panel_1 to the right
4732        panel_1.update(cx, |panel_1, cx| {
4733            panel_1.set_position(DockPosition::Right, cx)
4734        });
4735
4736        workspace.update(cx, |workspace, cx| {
4737            // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right.
4738            // Since it was the only panel on the left, the left dock should now be closed.
4739            assert!(!workspace.left_dock().read(cx).is_open());
4740            assert!(workspace.left_dock().read(cx).visible_panel().is_none());
4741            let right_dock = workspace.right_dock();
4742            assert_eq!(
4743                right_dock.read(cx).visible_panel().unwrap().id(),
4744                panel_1.id()
4745            );
4746            assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4747
4748            // Now we move panel_2Β to the left
4749            panel_2.set_position(DockPosition::Left, cx);
4750        });
4751
4752        workspace.update(cx, |workspace, cx| {
4753            // Since panel_2 was not visible on the right, we don't open the left dock.
4754            assert!(!workspace.left_dock().read(cx).is_open());
4755            // And the right dock is unaffected in it's displaying of panel_1
4756            assert!(workspace.right_dock().read(cx).is_open());
4757            assert_eq!(
4758                workspace
4759                    .right_dock()
4760                    .read(cx)
4761                    .visible_panel()
4762                    .unwrap()
4763                    .id(),
4764                panel_1.id()
4765            );
4766        });
4767
4768        // Move panel_1 back to the left
4769        panel_1.update(cx, |panel_1, cx| {
4770            panel_1.set_position(DockPosition::Left, cx)
4771        });
4772
4773        workspace.update(cx, |workspace, cx| {
4774            // Since panel_1 was visible on the right, we open the left dock and make panel_1 active.
4775            let left_dock = workspace.left_dock();
4776            assert!(left_dock.read(cx).is_open());
4777            assert_eq!(
4778                left_dock.read(cx).visible_panel().unwrap().id(),
4779                panel_1.id()
4780            );
4781            assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.);
4782            // And right the dock should be closed as it no longer has any panels.
4783            assert!(!workspace.right_dock().read(cx).is_open());
4784
4785            // Now we move panel_1 to the bottom
4786            panel_1.set_position(DockPosition::Bottom, cx);
4787        });
4788
4789        workspace.update(cx, |workspace, cx| {
4790            // Since panel_1 was visible on the left, we close the left dock.
4791            assert!(!workspace.left_dock().read(cx).is_open());
4792            // The bottom dock is sized based on the panel's default size,
4793            // since the panel orientation changed from vertical to horizontal.
4794            let bottom_dock = workspace.bottom_dock();
4795            assert_eq!(
4796                bottom_dock.read(cx).active_panel_size(cx).unwrap(),
4797                panel_1.size(cx),
4798            );
4799            // Close bottom dock and move panel_1 back to the left.
4800            bottom_dock.update(cx, |bottom_dock, cx| bottom_dock.set_open(false, cx));
4801            panel_1.set_position(DockPosition::Left, cx);
4802        });
4803
4804        // Emit activated event on panel 1
4805        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Activated));
4806
4807        // Now the left dock is open and panel_1 is active and focused.
4808        workspace.read_with(cx, |workspace, cx| {
4809            let left_dock = workspace.left_dock();
4810            assert!(left_dock.read(cx).is_open());
4811            assert_eq!(
4812                left_dock.read(cx).visible_panel().unwrap().id(),
4813                panel_1.id()
4814            );
4815            assert!(panel_1.is_focused(cx));
4816        });
4817
4818        // Emit closed event on panel 2, which is not active
4819        panel_2.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4820
4821        // Wo don't close the left dock, because panel_2 wasn't the active panel
4822        workspace.read_with(cx, |workspace, cx| {
4823            let left_dock = workspace.left_dock();
4824            assert!(left_dock.read(cx).is_open());
4825            assert_eq!(
4826                left_dock.read(cx).visible_panel().unwrap().id(),
4827                panel_1.id()
4828            );
4829        });
4830
4831        // Emitting a ZoomIn event shows the panel as zoomed.
4832        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn));
4833        workspace.read_with(cx, |workspace, _| {
4834            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4835            assert_eq!(workspace.zoomed_position, Some(DockPosition::Left));
4836        });
4837
4838        // Move panel to another dock while it is zoomed
4839        panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx));
4840        workspace.read_with(cx, |workspace, _| {
4841            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4842            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4843        });
4844
4845        // If focus is transferred to another view that's not a panel or another pane, we still show
4846        // the panel as zoomed.
4847        let focus_receiver = cx.add_view(window_id, |_| EmptyView);
4848        focus_receiver.update(cx, |_, cx| cx.focus_self());
4849        workspace.read_with(cx, |workspace, _| {
4850            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4851            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4852        });
4853
4854        // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed.
4855        workspace.update(cx, |_, cx| cx.focus_self());
4856        workspace.read_with(cx, |workspace, _| {
4857            assert_eq!(workspace.zoomed, None);
4858            assert_eq!(workspace.zoomed_position, None);
4859        });
4860
4861        // If focus is transferred again to another view that's not a panel or a pane, we won't
4862        // show the panel as zoomed because it wasn't zoomed before.
4863        focus_receiver.update(cx, |_, cx| cx.focus_self());
4864        workspace.read_with(cx, |workspace, _| {
4865            assert_eq!(workspace.zoomed, None);
4866            assert_eq!(workspace.zoomed_position, None);
4867        });
4868
4869        // When focus is transferred back to the panel, it is zoomed again.
4870        panel_1.update(cx, |_, cx| cx.focus_self());
4871        workspace.read_with(cx, |workspace, _| {
4872            assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any()));
4873            assert_eq!(workspace.zoomed_position, Some(DockPosition::Right));
4874        });
4875
4876        // Emitting a ZoomOut event unzooms the panel.
4877        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut));
4878        workspace.read_with(cx, |workspace, _| {
4879            assert_eq!(workspace.zoomed, None);
4880            assert_eq!(workspace.zoomed_position, None);
4881        });
4882
4883        // Emit closed event on panel 1, which is active
4884        panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::Closed));
4885
4886        // Now the left dock is closed, because panel_1 was the active panel
4887        workspace.read_with(cx, |workspace, cx| {
4888            let right_dock = workspace.right_dock();
4889            assert!(!right_dock.read(cx).is_open());
4890        });
4891    }
4892
4893    pub fn init_test(cx: &mut TestAppContext) {
4894        cx.foreground().forbid_parking();
4895        cx.update(|cx| {
4896            cx.set_global(SettingsStore::test(cx));
4897            theme::init((), cx);
4898            language::init(cx);
4899            crate::init_settings(cx);
4900            Project::init_settings(cx);
4901        });
4902    }
4903}