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